diff --git a/core/os/os.cpp b/core/os/os.cpp index 6e5dab9a8612..d450407c9056 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -30,6 +30,7 @@ #include "os.h" +#include "core/io/json.h" #include "core/os/dir_access.h" #include "core/os/file_access.h" #include "core/os/input.h" @@ -931,6 +932,58 @@ void OS::add_frame_delay(bool p_can_draw) { } } +void OS::set_use_benchmark(bool p_use_benchmark) { + use_benchmark = p_use_benchmark; +} + +bool OS::is_use_benchmark_set() { + return use_benchmark; +} + +void OS::set_benchmark_file(const String &p_benchmark_file) { + benchmark_file = p_benchmark_file; +} + +String OS::get_benchmark_file() { + return benchmark_file; +} + +void OS::benchmark_begin_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + start_benchmark_from[p_what] = OS::get_singleton()->get_ticks_usec(); +#endif +} +void OS::benchmark_end_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + uint64_t total = OS::get_singleton()->get_ticks_usec() - start_benchmark_from[p_what]; + double total_f = double(total) / double(1000000); + + startup_benchmark_json[p_what] = total_f; +#endif +} + +void OS::benchmark_dump() { +#ifdef TOOLS_ENABLED + if (!use_benchmark) { + return; + } + if (!benchmark_file.empty()) { + FileAccess *f = FileAccess::open(benchmark_file, FileAccess::WRITE); + if (f) { + f->store_string(JSON::print(startup_benchmark_json, "\t", false)); + } + } else { + List keys; + startup_benchmark_json.get_key_list(&keys); + print_line("BENCHMARK:"); + for (List::Element *E = keys.front(); E; E = E->next()) { + Variant &K = E->get(); + print_line("\t-" + K.operator String() + ": " + startup_benchmark_json[K] + " sec."); + } + } +#endif +} + OS::OS() { void *volatile stack_bottom; diff --git a/core/os/os.h b/core/os/os.h index 85270d3e1ed3..d67e9d61250e 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -75,6 +75,12 @@ class OS { bool restart_on_exit; List restart_commandline; + // For tracking benchmark data + bool use_benchmark = false; + String benchmark_file; + HashMap start_benchmark_from; + Dictionary startup_benchmark_json; + protected: bool _update_pending; @@ -663,6 +669,15 @@ class OS { virtual bool request_permissions() { return true; } virtual Vector get_granted_permissions() const { return Vector(); } + // For recording / measuring benchmark data. Only enabled with tools + void set_use_benchmark(bool p_use_benchmark); + bool is_use_benchmark_set(); + void set_benchmark_file(const String &p_benchmark_file); + String get_benchmark_file(); + virtual void benchmark_begin_measure(const String &p_what); + virtual void benchmark_end_measure(const String &p_what); + virtual void benchmark_dump(); + virtual void process_and_drop_events() {} OS(); virtual ~OS(); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 3f8855ee8ff5..6f969e1cddcb 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -101,6 +101,7 @@ extern void register_variant_methods(); extern void unregister_variant_methods(); void register_core_types() { + OS::get_singleton()->benchmark_begin_measure("register_core_types"); MemoryPool::setup(); StringName::setup(); @@ -225,6 +226,8 @@ void register_core_types() { _classdb = memnew(_ClassDB); _marshalls = memnew(_Marshalls); _json = memnew(_JSON); + + OS::get_singleton()->benchmark_end_measure("register_core_types"); } void register_core_settings() { @@ -272,6 +275,8 @@ void register_core_singletons() { } void unregister_core_types() { + OS::get_singleton()->benchmark_begin_measure("unregister_core_types"); + memdelete(_resource_loader); memdelete(_resource_saver); memdelete(_os); @@ -320,4 +325,6 @@ void unregister_core_types() { StringName::cleanup(); MemoryPool::cleanup(); + + OS::get_singleton()->benchmark_end_measure("unregister_core_types"); } diff --git a/doc/classes/VisibilityEnabler2D.xml b/doc/classes/VisibilityEnabler2D.xml index 64ed5eab6df1..38f56deab6f8 100644 --- a/doc/classes/VisibilityEnabler2D.xml +++ b/doc/classes/VisibilityEnabler2D.xml @@ -47,6 +47,9 @@ If [code]true[/code], the parent's [method Node._process] will be stopped. + + If [code]true[/code] and the parent is a [CanvasItem], the parent will be hidden. + @@ -67,7 +70,9 @@ This enabler will stop [AnimatedSprite] nodes animations. - + + + Represents the size of the [enum Enabler] enum. diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp new file mode 100644 index 000000000000..7b918ea5530a --- /dev/null +++ b/editor/directory_create_dialog.cpp @@ -0,0 +1,178 @@ +/**************************************************************************/ +/* directory_create_dialog.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 "directory_create_dialog.h" + +#include "core/os/dir_access.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/panel_container.h" + +static String sanitize_input(const String &p_path) { + String path = p_path.strip_edges(); + if (path.ends_with("/")) { + path = path.left(path.length() - 1); + } + return path; +} + +String DirectoryCreateDialog::_validate_path(const String &p_path) const { + if (p_path.empty()) { + return TTR("Folder name cannot be empty."); + } + + const Vector parts = p_path.split("/"); + for (int i = 0; i < parts.size(); i++) { + const String part = parts[i]; + if (part.empty()) { + return TTR("Folder name cannot be empty."); + } + if (p_path.find("\\") != -1 || p_path.find(":") != -1 || p_path.find("*") != -1 || + p_path.find("|") != -1 || p_path.find(">") != -1 || p_path.ends_with(".") || p_path.ends_with(" ")) { + return TTR("Folder name contains invalid characters."); + } + } + + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->change_dir(base_dir); + if (da->file_exists(p_path)) { + return TTR("File with that name already exists."); + } + if (da->dir_exists(p_path)) { + return TTR("Folder with that name already exists."); + } + + return String(); +} + +void DirectoryCreateDialog::_on_dir_path_changed(const String &p_text) { + const String path = sanitize_input(p_text); + const String error = _validate_path(path); + + if (error.empty()) { + status_label->add_color_override("font_color", get_color("success_color", "Editor")); + + if (path.find("/") != -1) { + status_label->set_text(TTR("Using slashes in folder names will create subfolders recursively.")); + } else { + status_label->set_text(TTR("Folder name is valid.")); + } + } else { + status_label->add_color_override("font_color", get_color("error_color", "Editor")); + status_label->set_text(error); + } + + get_ok()->set_disabled(!error.empty()); +} + +void DirectoryCreateDialog::ok_pressed() { + const String path = sanitize_input(dir_path->get_text()); + + // The OK button should be disabled if the path is invalid, but just in case. + const String error = _validate_path(path); + ERR_FAIL_COND_MSG(!error.empty(), error); + + Error err; + DirAccessRef da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + + err = da->change_dir(base_dir); + ERR_FAIL_COND_MSG(err != OK, "Cannot open directory '" + base_dir + "'."); + + print_verbose("Making folder " + path + " in " + base_dir); + err = da->make_dir_recursive(path); + + if (err == OK) { + emit_signal("dir_created"); + } else { + EditorNode::get_singleton()->show_warning(TTR("Could not create folder.")); + } + hide(); +} + +void DirectoryCreateDialog::_post_popup() { + ConfirmationDialog::_post_popup(); + label->set_text(vformat(TTR("Create new folder in %s:"), base_dir)); + minimum_size_changed(); + dir_path->grab_focus(); +} + +void DirectoryCreateDialog::config(const String &p_base_dir) { + base_dir = p_base_dir; + label->set_text(""); // Set the correct text later in _post_popup to avoid GH-47005. + dir_path->set_text("new folder"); + dir_path->select_all(); + _on_dir_path_changed(dir_path->get_text()); +} + +void DirectoryCreateDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_on_dir_path_changed"), &DirectoryCreateDialog::_on_dir_path_changed); + + ADD_SIGNAL(MethodInfo("dir_created")); +} + +void DirectoryCreateDialog::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + status_panel->add_style_override("panel", get_stylebox("bg", "Tree")); + } break; + } +} + +DirectoryCreateDialog::DirectoryCreateDialog() { + set_title(TTR("Create Folder")); + set_custom_minimum_size(Size2i(480, 0) * EDSCALE); + + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + + label = memnew(Label); + label->set_autowrap(true); + vb->add_child(label); + + dir_path = memnew(LineEdit); + dir_path->connect("text_changed", this, "_on_dir_path_changed"); + vb->add_child(dir_path); + register_text_enter(dir_path); + + Control *spacing = memnew(Control); + spacing->set_custom_minimum_size(Size2(0, 10 * EDSCALE)); + vb->add_child(spacing); + + status_panel = memnew(PanelContainer); + status_panel->set_v_size_flags(Control::SIZE_EXPAND_FILL); + vb->add_child(status_panel); + + status_label = memnew(Label); + status_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + status_panel->add_child(status_label); +} diff --git a/editor/directory_create_dialog.h b/editor/directory_create_dialog.h new file mode 100644 index 000000000000..dc4ff2d934b8 --- /dev/null +++ b/editor/directory_create_dialog.h @@ -0,0 +1,68 @@ +/**************************************************************************/ +/* directory_create_dialog.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 DIRECTORY_CREATE_DIALOG_H +#define DIRECTORY_CREATE_DIALOG_H + +#include "scene/gui/dialogs.h" + +class Label; +class LineEdit; +class PanelContainer; + +class DirectoryCreateDialog : public ConfirmationDialog { + GDCLASS(DirectoryCreateDialog, ConfirmationDialog); + + String base_dir; + + Label *label = nullptr; + LineEdit *dir_path = nullptr; + + PanelContainer *status_panel = nullptr; + Label *status_label = nullptr; + + String _validate_path(const String &p_path) const; + + void _on_dir_path_changed(const String &p_text); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void ok_pressed(); + virtual void _post_popup(); + +public: + void config(const String &p_base_dir); + + DirectoryCreateDialog(); +}; + +#endif // DIRECTORY_CREATE_DIALOG_H diff --git a/editor/editor_fonts.cpp b/editor/editor_fonts.cpp index 8f068db14d93..1d73a5a62b89 100644 --- a/editor/editor_fonts.cpp +++ b/editor/editor_fonts.cpp @@ -32,6 +32,7 @@ #include "builtin_fonts.gen.h" #include "core/os/dir_access.h" +#include "core/os/os.h" #include "editor_scale.h" #include "editor_settings.h" #include "scene/resources/default_theme/default_theme.h" @@ -100,6 +101,7 @@ MAKE_FALLBACKS(m_name); void editor_register_fonts(Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("editor_register_fonts"); DirAccess *dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); /* Custom font */ @@ -285,4 +287,6 @@ void editor_register_fonts(Ref p_theme) { MAKE_SOURCE_FONT(df_text_editor_status_code, default_font_size); p_theme->set_font("status_source", "EditorFonts", df_text_editor_status_code); + + OS::get_singleton()->benchmark_end_measure("editor_register_fonts"); } diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index bb725a3f4ce6..88f5e6f63159 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -558,6 +558,10 @@ void EditorNode::_notification(int p_what) { case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { scene_tabs->set_tab_close_display_policy((bool(EDITOR_GET("interface/scene_tabs/always_show_close_button")) ? Tabs::CLOSE_BUTTON_SHOW_ALWAYS : Tabs::CLOSE_BUTTON_SHOW_ACTIVE_ONLY)); + FileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files")); + EditorFileDialog::set_default_show_hidden_files(EditorSettings::get_singleton()->get("filesystem/file_dialog/show_hidden_files")); + EditorFileDialog::set_default_display_mode((EditorFileDialog::DisplayMode)EditorSettings::get_singleton()->get("filesystem/file_dialog/display_mode").operator int()); + theme = create_custom_theme(theme_base->get_theme()); theme_base->set_theme(theme); @@ -867,8 +871,11 @@ void EditorNode::_sources_changed(bool p_exist) { _load_docks(); if (defer_load_scene != "") { + OS::get_singleton()->benchmark_begin_measure("editor_load_scene"); load_scene(defer_load_scene); defer_load_scene = ""; + OS::get_singleton()->benchmark_end_measure("editor_load_scene"); + OS::get_singleton()->benchmark_dump(); } } } @@ -3901,6 +3908,8 @@ bool EditorNode::is_scene_in_use(const String &p_path) { } void EditorNode::register_editor_types() { + OS::get_singleton()->benchmark_begin_measure("register_editor_types"); + ResourceLoader::set_timestamp_on_load(true); ResourceSaver::set_timestamp_on_save(true); @@ -3936,12 +3945,18 @@ void EditorNode::register_editor_types() { // FIXME: Is this stuff obsolete, or should it be ported to new APIs? ClassDB::register_class(); //ClassDB::register_type(); + + OS::get_singleton()->benchmark_end_measure("register_editor_types"); } void EditorNode::unregister_editor_types() { + OS::get_singleton()->benchmark_begin_measure("unregister_editor_types"); + _init_callbacks.clear(); EditorResourcePicker::clear_caches(); + + OS::get_singleton()->benchmark_end_measure("unregister_editor_types"); } void EditorNode::stop_child_process() { @@ -5853,6 +5868,7 @@ int EditorNode::execute_and_show_output(const String &p_title, const String &p_p } EditorNode::EditorNode() { + OS::get_singleton()->benchmark_begin_measure("editor"); EditorPropertyNameProcessor *epnp = memnew(EditorPropertyNameProcessor); add_child(epnp); @@ -7247,6 +7263,8 @@ EditorNode::EditorNode() { String exec = OS::get_singleton()->get_executable_path(); EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "executable_path", exec); // Save editor executable path for third-party tools + + OS::get_singleton()->benchmark_end_measure("editor"); } EditorNode::~EditorNode() { diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 320e56cb80b9..6172f2a1999d 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -796,53 +796,72 @@ class EditorPropertyLayersGrid : public Control { return String(); } - void _gui_input(const Ref &p_ev) { - const Ref mm = p_ev; - if (mm.is_valid()) { - bool expand_was_hovered = expand_hovered; - expand_hovered = expand_rect.has_point(mm->get_position()); - if (expand_hovered != expand_was_hovered) { - update(); - } + void _update_hovered(const Vector2 &p_position) { + bool expand_was_hovered = expand_hovered; + expand_hovered = expand_rect.has_point(p_position); + if (expand_hovered != expand_was_hovered) { + update(); + } - if (!expand_hovered) { - for (int i = 0; i < flag_rects.size(); i++) { - if (flag_rects[i].has_point(mm->get_position())) { - // Used to highlight the hovered flag in the layers grid. - hovered_index = i; - update(); - return; - } + if (!expand_hovered) { + for (int i = 0; i < flag_rects.size(); i++) { + if (flag_rects[i].has_point(p_position)) { + // Used to highlight the hovered flag in the layers grid. + hovered_index = i; + update(); + return; } } + } + + // Remove highlight when no square is hovered. + if (hovered_index != -1) { + hovered_index = -1; + update(); + } + } + + void _on_hover_exit() { + if (expand_hovered) { + expand_hovered = false; + update(); + } + if (hovered_index != -1) { + hovered_index = -1; + update(); + } + } - // Remove highlight when no square is hovered. - if (hovered_index != -1) { - hovered_index = -1; - update(); + void _update_flag() { + if (hovered_index >= 0) { + // Toggle the flag. + // We base our choice on the hovered flag, so that it always matches the hovered flag. + if (value & (1 << hovered_index)) { + value &= ~(1 << hovered_index); + } else { + value |= (1 << hovered_index); } + emit_signal("flag_changed", value); + update(); + } else if (expand_hovered) { + expanded = !expanded; + minimum_size_changed(); + update(); + } + } + + void _gui_input(const Ref &p_ev) { + const Ref mm = p_ev; + if (mm.is_valid()) { + _update_hovered(mm->get_position()); return; } const Ref mb = p_ev; if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT && mb->is_pressed()) { - if (hovered_index >= 0) { - // Toggle the flag. - // We base our choice on the hovered flag, so that it always matches the hovered flag. - if (value & (1 << hovered_index)) { - value &= ~(1 << hovered_index); - } else { - value |= (1 << hovered_index); - } - - emit_signal("flag_changed", value); - update(); - } else if (expand_hovered) { - expanded = !expanded; - minimum_size_changed(); - update(); - } + _update_hovered(mb->get_position()); + _update_flag(); } } @@ -974,14 +993,7 @@ class EditorPropertyLayersGrid : public Control { } break; case NOTIFICATION_MOUSE_EXIT: { - if (expand_hovered) { - expand_hovered = false; - update(); - } - if (hovered_index != -1) { - hovered_index = -1; - update(); - } + _on_hover_exit(); } break; default: diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 4d4f14b62d1c..b1206f96cef3 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -31,6 +31,7 @@ #include "editor_themes.h" #include "core/io/resource_loader.h" +#include "core/os/os.h" #include "editor_fonts.h" #include "editor_icons.gen.h" #include "editor_scale.h" @@ -134,6 +135,7 @@ static Ref editor_generate_icon(int p_index, bool p_convert_color, #endif void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme = true, int p_thumb_size = 32, bool p_only_thumbs = false) { + OS::get_singleton()->benchmark_begin_measure("editor_register_and_generate_icons_" + String((p_only_thumbs ? "with_only_thumbs" : "all"))); #ifdef MODULE_SVG_ENABLED // The default icon theme is designed to be used for a dark theme. // This dictionary stores color codes to convert to other colors @@ -290,9 +292,11 @@ void editor_register_and_generate_icons(Ref p_theme, bool p_dark_theme = #else WARN_PRINT("SVG support disabled, editor icons won't be rendered."); #endif + OS::get_singleton()->benchmark_end_measure("editor_register_and_generate_icons_" + String((p_only_thumbs ? "with_only_thumbs" : "all"))); } Ref create_editor_theme(const Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("create_editor_theme"); Ref theme = Ref(memnew(Theme)); const float default_contrast = 0.25; @@ -1419,10 +1423,13 @@ Ref create_editor_theme(const Ref p_theme) { setting->load_text_editor_theme(); } + OS::get_singleton()->benchmark_end_measure("create_editor_theme"); + return theme; } Ref create_custom_theme(const Ref p_theme) { + OS::get_singleton()->benchmark_begin_measure("create_custom_theme"); Ref theme = create_editor_theme(p_theme); const String custom_theme_path = EditorSettings::get_singleton()->get("interface/theme/custom_theme"); @@ -1433,6 +1440,7 @@ Ref create_custom_theme(const Ref p_theme) { } } + OS::get_singleton()->benchmark_end_measure("create_custom_theme"); return theme; } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 8866559a4c48..566206f83c0c 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -37,6 +37,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/project_settings.h" +#include "editor/directory_create_dialog.h" #include "editor/scene_create_dialog.h" #include "editor_feature_profile.h" #include "editor_node.h" @@ -1344,38 +1345,6 @@ void FileSystemDock::_save_scenes_after_move(const Map &p_rename editor->save_scene_list(new_filenames); } -void FileSystemDock::_make_dir_confirm() { - String dir_name = make_dir_dialog_text->get_text().strip_edges(); - - if (dir_name.length() == 0) { - EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; - } else if (dir_name.find("/") != -1 || dir_name.find("\\") != -1 || dir_name.find(":") != -1 || dir_name.find("*") != -1 || - dir_name.find("|") != -1 || dir_name.find(">") != -1 || dir_name.ends_with(".") || dir_name.ends_with(" ")) { - EditorNode::get_singleton()->show_warning(TTR("Provided name contains invalid characters.")); - return; - } - - String directory = path; - if (!directory.ends_with("/")) { - directory = directory.get_base_dir(); - } - print_verbose("Making folder " + dir_name + " in " + directory); - DirAccess *da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - Error err = da->change_dir(directory); - if (err == OK) { - err = da->make_dir(dir_name); - } - memdelete(da); - - if (err == OK) { - print_verbose("FileSystem: calling rescan."); - _rescan(); - } else { - EditorNode::get_singleton()->show_warning(TTR("Could not create folder.")); - } -} - void FileSystemDock::_make_scene_confirm() { const String scene_path = make_scene_dialog->get_scene_path(); @@ -1877,10 +1846,12 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } break; case FILE_NEW_FOLDER: { - make_dir_dialog_text->set_text("new folder"); - make_dir_dialog_text->select_all(); - make_dir_dialog->popup_centered_minsize(Size2(250, 80) * EDSCALE); - make_dir_dialog_text->grab_focus(); + String directory = path; + if (!directory.ends_with("/")) { + directory = directory.get_base_dir(); + } + make_dir_dialog->config(directory); + make_dir_dialog->popup_centered(Size2(1, 1)); } break; case FILE_NEW_SCENE: { @@ -2744,7 +2715,6 @@ void FileSystemDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_bw_history"), &FileSystemDock::_bw_history); ClassDB::bind_method(D_METHOD("_fs_changed"), &FileSystemDock::_fs_changed); ClassDB::bind_method(D_METHOD("_tree_multi_selected"), &FileSystemDock::_tree_multi_selected); - ClassDB::bind_method(D_METHOD("_make_dir_confirm"), &FileSystemDock::_make_dir_confirm); ClassDB::bind_method(D_METHOD("_make_scene_confirm"), &FileSystemDock::_make_scene_confirm); ClassDB::bind_method(D_METHOD("_resource_created"), &FileSystemDock::_resource_created); ClassDB::bind_method(D_METHOD("_move_operation_confirm", "to_path", "overwrite"), &FileSystemDock::_move_operation_confirm, DEFVAL(false)); @@ -2959,16 +2929,9 @@ FileSystemDock::FileSystemDock(EditorNode *p_editor) { duplicate_dialog->register_text_enter(duplicate_dialog_text); duplicate_dialog->connect("confirmed", this, "_duplicate_operation_confirm"); - make_dir_dialog = memnew(ConfirmationDialog); - make_dir_dialog->set_title(TTR("Create Folder")); - VBoxContainer *make_folder_dialog_vb = memnew(VBoxContainer); - make_dir_dialog->add_child(make_folder_dialog_vb); - - make_dir_dialog_text = memnew(LineEdit); - make_folder_dialog_vb->add_margin_child(TTR("Name:"), make_dir_dialog_text); + make_dir_dialog = memnew(DirectoryCreateDialog); add_child(make_dir_dialog); - make_dir_dialog->register_text_enter(make_dir_dialog_text); - make_dir_dialog->connect("confirmed", this, "_make_dir_confirm"); + make_dir_dialog->connect("dir_created", this, "_rescan"); make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index e1ad11e84a2f..0031a531d2d0 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -56,6 +56,7 @@ class EditorNode; class SceneCreateDialog; +class DirectoryCreateDialog; class FileSystemDock : public VBoxContainer { GDCLASS(FileSystemDock, VBoxContainer); @@ -153,8 +154,7 @@ class FileSystemDock : public VBoxContainer { LineEdit *rename_dialog_text; ConfirmationDialog *duplicate_dialog; LineEdit *duplicate_dialog_text; - ConfirmationDialog *make_dir_dialog; - LineEdit *make_dir_dialog_text; + DirectoryCreateDialog *make_dir_dialog = nullptr; ConfirmationDialog *overwrite_dialog; SceneCreateDialog *make_scene_dialog = nullptr; ScriptCreateDialog *make_script_dialog; @@ -229,7 +229,6 @@ class FileSystemDock : public VBoxContainer { void _folder_removed(String p_folder); void _resource_created() const; - void _make_dir_confirm(); void _make_scene_confirm(); void _rename_operation_confirm(); void _duplicate_operation_confirm(); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 461ac438f522..97e2617fb4af 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -2391,6 +2391,7 @@ void ProjectManager::_version_button_pressed() { } ProjectManager::ProjectManager() { + OS::get_singleton()->benchmark_begin_measure("project_manager"); // load settings if (!EditorSettings::get_singleton()) { EditorSettings::create(); @@ -2791,6 +2792,8 @@ ProjectManager::ProjectManager() { about = memnew(EditorAbout); add_child(about); + + OS::get_singleton()->benchmark_end_measure("project_manager"); } ProjectManager::~ProjectManager() { diff --git a/main/main.cpp b/main/main.cpp index df07d5fed295..52284788d5d5 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -349,6 +349,8 @@ void Main::print_help(const char *p_binary) { OS::get_singleton()->print(" --doctool [] Dump the engine API reference to the given (defaults to current dir) in XML format, merging if existing files are found.\n"); OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n"); OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n"); + OS::get_singleton()->print(" --benchmark Benchmark the run time and print it to console.\n"); + OS::get_singleton()->print(" --benchmark-file Benchmark the run time and save it to a given file in JSON format. The path should be absolute.\n"); #ifdef DEBUG_METHODS_ENABLED OS::get_singleton()->print(" --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n"); #endif @@ -399,9 +401,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->initialize_core(); + // Benchmark tracking must be done after `OS::get_singleton()->initialize_core()` as on some + // platforms, it's used to set up the time utilities. + OS::get_singleton()->benchmark_begin_measure("startup_begin"); + engine = memnew(Engine); MAIN_PRINT("Main: Initialize CORE"); + OS::get_singleton()->benchmark_begin_measure("core"); register_core_types(); register_core_driver_types(); @@ -921,6 +928,20 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph OS::get_singleton()->disable_crash_handler(); } else if (I->get() == "--skip-breakpoints") { skip_breakpoints = true; + } else if (I->get() == "--benchmark") { + OS::get_singleton()->set_use_benchmark(true); + } else if (I->get() == "--benchmark-file") { + if (I->next()) { + OS::get_singleton()->set_use_benchmark(true); + String benchmark_file = I->next()->get(); + OS::get_singleton()->set_benchmark_file(benchmark_file); + N = I->next()->next(); + } else { + OS::get_singleton()->print("Missing argument for --startup-benchmark-file .\n"); + OS::get_singleton()->print("Missing argument for --benchmark-file .\n"); + goto error; + } + } else { main_args.push_back(I->get()); } @@ -1299,7 +1320,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (p_second_phase) { return setup2(); } - + OS::get_singleton()->benchmark_end_measure("core"); return OK; error: @@ -1352,6 +1373,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (message_queue) { memdelete(message_queue); } + + OS::get_singleton()->benchmark_end_measure("core"); + OS::get_singleton()->finalize_core(); locale = String(); @@ -2197,6 +2221,8 @@ bool Main::start() { } } + OS::get_singleton()->benchmark_end_measure("startup_begin"); + OS::get_singleton()->benchmark_dump(); return true; } @@ -2462,6 +2488,7 @@ void Main::force_redraw() { * The order matters as some of those steps are linked with each other. */ void Main::cleanup(bool p_force) { + OS::get_singleton()->benchmark_begin_measure("Main::cleanup"); if (!p_force) { ERR_FAIL_COND(!_start_success); } @@ -2581,6 +2608,9 @@ void Main::cleanup(bool p_force) { unregister_core_driver_types(); unregister_core_types(); + OS::get_singleton()->benchmark_end_measure("Main::cleanup"); + OS::get_singleton()->benchmark_dump(); + OS::get_singleton()->finalize_core(); #ifdef RID_HANDLES_ENABLED diff --git a/misc/dist/shell/_godot.zsh-completion b/misc/dist/shell/_godot.zsh-completion index 0a147dcf9d24..9da65794b5c4 100644 --- a/misc/dist/shell/_godot.zsh-completion +++ b/misc/dist/shell/_godot.zsh-completion @@ -74,4 +74,6 @@ _arguments \ '--no-docbase[disallow dumping the base types (used with --doctool)]' \ '--build-solutions[build the scripting solutions (e.g. for C# projects)]' \ '--gdnative-generate-json-api[generate JSON dump of the Godot API for GDNative bindings]' \ + '--benchmark[benchmark the run time and print it to console]' \ + '--benchmark-file[benchmark the run time and save it to a given file in JSON format]:path to output JSON file' \ '--test[run a unit test]:unit test name' diff --git a/misc/dist/shell/godot.bash-completion b/misc/dist/shell/godot.bash-completion index d0638c0b3927..af6b4a44178a 100644 --- a/misc/dist/shell/godot.bash-completion +++ b/misc/dist/shell/godot.bash-completion @@ -77,6 +77,8 @@ _complete_godot_options() { --no-docbase --build-solutions --gdnative-generate-json-api +--benchmark +--benchmark-file --test " -- "$1")) } diff --git a/misc/dist/shell/godot.fish b/misc/dist/shell/godot.fish index 57fa90e053bb..338c24796a0c 100644 --- a/misc/dist/shell/godot.fish +++ b/misc/dist/shell/godot.fish @@ -88,4 +88,6 @@ complete -c godot -l doctool -d "Dump the engine API reference to the given path complete -c godot -l no-docbase -d "Disallow dumping the base types (used with --doctool)" complete -c godot -l build-solutions -d "Build the scripting solutions (e.g. for C# projects)" complete -c godot -l gdnative-generate-json-api -d "Generate JSON dump of the Godot API for GDNative bindings" +complete -c godot -l benchmark -d "Benchmark the run time and print it to console" +complete -c godot -l benchmark-file -d "Benchmark the run time and save it to a given file in JSON format" -x complete -c godot -l test -d "Run a unit test" -x diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle index 72f253e62336..1710887b2ff8 100644 --- a/platform/android/java/app/config.gradle +++ b/platform/android/java/app/config.gradle @@ -1,5 +1,5 @@ ext.versions = [ - androidGradlePlugin: '7.3.0', + androidGradlePlugin: '7.2.1', compileSdk : 33, minSdk : 19, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_MIN_SDK_VERSION' targetSdk : 33, // Also update 'platform/android/export/export_plugin.cpp#DEFAULT_TARGET_SDK_VERSION' diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index 71385315aeef..239047f2c83c 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -104,6 +104,9 @@ open class GodotEditor : FullScreenGodotApp() { if (args != null && args.isNotEmpty()) { commandLineParams.addAll(listOf(*args)) } + if (BuildConfig.BUILD_TYPE == "dev") { + commandLineParams.add("--benchmark") + } } override fun getCommandLine() = commandLineParams diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java index a55fe567e075..f679e169ae7f 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java @@ -39,6 +39,7 @@ import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.GodotPluginRegistry; import org.godotengine.godot.tts.GodotTTS; +import org.godotengine.godot.utils.BenchmarkUtils; import org.godotengine.godot.utils.GodotNetUtils; import org.godotengine.godot.utils.PermissionsUtil; import org.godotengine.godot.xr.XRMode; @@ -268,6 +269,8 @@ protected void instanceSingleton(SingletonBase s) { public GodotIO io; public GodotNetUtils netUtils; public GodotTTS tts; + private DirectoryAccessHandler directoryAccessHandler; + private FileAccessHandler fileAccessHandler; static SingletonBase[] singletons = new SingletonBase[MAX_SINGLETONS]; static int singleton_count = 0; @@ -601,7 +604,7 @@ private String[] parseCommandLine() { } return cmdline; } catch (Exception e) { - e.printStackTrace(); + // The _cl_ file can be missing with no adverse effect return new String[0]; } } @@ -662,8 +665,8 @@ private void initializeGodot() { netUtils = new GodotNetUtils(activity); tts = new GodotTTS(activity); Context context = getContext(); - DirectoryAccessHandler directoryAccessHandler = new DirectoryAccessHandler(context); - FileAccessHandler fileAccessHandler = new FileAccessHandler(context); + directoryAccessHandler = new DirectoryAccessHandler(context); + fileAccessHandler = new FileAccessHandler(context); mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mGravity = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); @@ -685,6 +688,7 @@ public void onServiceConnected(Messenger m) { @Override public void onCreate(Bundle icicle) { + BenchmarkUtils.beginBenchmarkMeasure("Godot::onCreate"); super.onCreate(icicle); final Activity activity = getActivity(); @@ -736,6 +740,18 @@ public void onCreate(Bundle icicle) { editor.putString("store_public_key", main_pack_key); editor.apply(); + i++; + } else if (command_line[i].equals("--benchmark")) { + BenchmarkUtils.setUseBenchmark(true); + new_args.add(command_line[i]); + } else if (has_extra && command_line[i].equals("--benchmark-file")) { + BenchmarkUtils.setUseBenchmark(true); + new_args.add(command_line[i]); + + // Retrieve the filepath + BenchmarkUtils.setBenchmarkFile(command_line[i + 1]); + new_args.add(command_line[i + 1]); + i++; } else if (command_line[i].trim().length() != 0) { new_args.add(command_line[i]); @@ -807,6 +823,7 @@ public void onCreate(Bundle icicle) { mCurrentIntent = activity.getIntent(); initializeGodot(); + BenchmarkUtils.endBenchmarkMeasure("Godot::onCreate"); } @Override @@ -1021,22 +1038,6 @@ public final void onAccuracyChanged(Sensor sensor, int accuracy) { // Do something here if sensor accuracy changes. } - /* - @Override public boolean dispatchKeyEvent(KeyEvent event) { - - if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) { - - System.out.printf("** BACK REQUEST!\n"); - - GodotLib.quit(); - return true; - } - System.out.printf("** OTHER KEY!\n"); - - return false; - } - */ - public void onBackPressed() { boolean shouldQuit = true; @@ -1242,6 +1243,16 @@ public void initInputDevices() { mView.initInputDevices(); } + @Keep + public DirectoryAccessHandler getDirectoryAccessHandler() { + return directoryAccessHandler; + } + + @Keep + public FileAccessHandler getFileAccessHandler() { + return fileAccessHandler; + } + @Keep private int createNewGodotInstance(String[] args) { if (godotHost != null) { @@ -1249,4 +1260,19 @@ private int createNewGodotInstance(String[] args) { } return 0; } + + @Keep + private void beginBenchmarkMeasure(String label) { + BenchmarkUtils.beginBenchmarkMeasure(label); + } + + @Keep + private void endBenchmarkMeasure(String label) { + BenchmarkUtils.endBenchmarkMeasure(label); + } + + @Keep + private void dumpBenchmark(String benchmarkFile) { + BenchmarkUtils.dumpBenchmark(fileAccessHandler, benchmarkFile); + } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotView.java index 98bc45f58a5f..07430a9beae2 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotView.java @@ -43,14 +43,21 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.PixelFormat; import android.os.Build; +import android.text.TextUtils; +import android.util.SparseArray; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.PointerIcon; import androidx.annotation.Keep; +import java.io.InputStream; + import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; @@ -79,6 +86,7 @@ public class GodotView extends GLSurfaceView { private final Godot godot; private final GodotInputHandler inputHandler; private final GodotRenderer godotRenderer; + private final SparseArray customPointerIcons = new SparseArray<>(); private EGLConfigChooser eglConfigChooser; private EGLContextFactory eglContextFactory; @@ -149,13 +157,48 @@ public void releasePointerCapture() { inputHandler.onPointerCaptureChange(false); } + /** + * Used to configure the PointerIcon for the given type. + * + * Called from JNI + */ + @Keep + public void configurePointerIcon(int pointerType, String imagePath, float hotSpotX, float hotSpotY) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + try { + Bitmap bitmap = null; + if (!TextUtils.isEmpty(imagePath)) { + if (godot.getDirectoryAccessHandler().filesystemFileExists(imagePath)) { + // Try to load the bitmap from the file system + bitmap = BitmapFactory.decodeFile(imagePath); + } else if (godot.getDirectoryAccessHandler().assetsFileExists(imagePath)) { + // Try to load the bitmap from the assets directory + AssetManager am = getContext().getAssets(); + InputStream imageInputStream = am.open(imagePath); + bitmap = BitmapFactory.decodeStream(imageInputStream); + } + } + + PointerIcon customPointerIcon = PointerIcon.create(bitmap, hotSpotX, hotSpotY); + customPointerIcons.put(pointerType, customPointerIcon); + } catch (Exception e) { + // Reset the custom pointer icon + customPointerIcons.delete(pointerType); + } + } + } + /** * Called from JNI to change the pointer icon */ @Keep private void setPointerIcon(int pointerType) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - setPointerIcon(PointerIcon.getSystemIcon(getContext(), pointerType)); + PointerIcon pointerIcon = customPointerIcons.get(pointerType); + if (pointerIcon == null) { + pointerIcon = PointerIcon.getSystemIcon(getContext(), pointerType); + } + setPointerIcon(pointerIcon); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt index 2d19167817f8..dd6d5180c553 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/directory/DirectoryAccessHandler.kt @@ -79,6 +79,9 @@ class DirectoryAccessHandler(context: Context) { private val assetsDirAccess = AssetsDirectoryAccess(context) private val fileSystemDirAccess = FilesystemDirectoryAccess(context) + fun assetsFileExists(assetsPath: String) = assetsDirAccess.fileExists(assetsPath) + fun filesystemFileExists(path: String) = fileSystemDirAccess.fileExists(path) + private fun hasDirId(accessType: AccessType, dirId: Int): Boolean { return when (accessType) { ACCESS_RESOURCES -> assetsDirAccess.hasDirId(dirId) diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt index 357008ca666c..984bf607d000 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt @@ -46,7 +46,7 @@ class FileAccessHandler(val context: Context) { private val TAG = FileAccessHandler::class.java.simpleName private const val FILE_NOT_FOUND_ERROR_ID = -1 - private const val INVALID_FILE_ID = 0 + internal const val INVALID_FILE_ID = 0 private const val STARTING_FILE_ID = 1 internal fun fileExists(context: Context, storageScopeIdentifier: StorageScope.Identifier, path: String?): Boolean { @@ -96,13 +96,17 @@ class FileAccessHandler(val context: Context) { private fun hasFileId(fileId: Int) = files.indexOfKey(fileId) >= 0 fun fileOpen(path: String?, modeFlags: Int): Int { + val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID + return fileOpen(path, accessFlag) + } + + internal fun fileOpen(path: String?, accessFlag: FileAccessFlags): Int { val storageScope = storageScopeIdentifier.identifyStorageScope(path) if (storageScope == StorageScope.UNKNOWN) { return INVALID_FILE_ID } try { - val accessFlag = FileAccessFlags.fromNativeModeFlags(modeFlags) ?: return INVALID_FILE_ID val dataAccess = DataAccess.generateDataAccess(storageScope, context, path!!, accessFlag) ?: return INVALID_FILE_ID files.put(++lastFileId, dataAccess) diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt new file mode 100644 index 000000000000..e78e310ef1c0 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/BenchmarkUtils.kt @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* BenchmarkUtils.kt */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +@file:JvmName("BenchmarkUtils") + +package org.godotengine.godot.utils + +import android.os.Build +import android.os.SystemClock +import android.os.Trace +import android.util.Log +import org.godotengine.godot.BuildConfig +import org.godotengine.godot.io.file.FileAccessFlags +import org.godotengine.godot.io.file.FileAccessHandler +import org.json.JSONObject +import java.nio.ByteBuffer +import java.util.concurrent.ConcurrentSkipListMap + +/** + * Contains benchmark related utilities methods + */ +private const val TAG = "GodotBenchmark" + +var useBenchmark = false +var benchmarkFile = "" + +private val startBenchmarkFrom = ConcurrentSkipListMap() +private val benchmarkTracker = ConcurrentSkipListMap() + +/** + * Start measuring and tracing the execution of a given section of code using the given label. + * + * Must be followed by a call to [endBenchmarkMeasure]. + * + * Note: Only enabled on 'editorDev' build variant. + */ +fun beginBenchmarkMeasure(label: String) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + startBenchmarkFrom[label] = SystemClock.elapsedRealtime() + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Trace.beginAsyncSection(label, 0) + } +} + +/** + * End measuring and tracing of the section of code with the given label. + * + * Must be preceded by a call [beginBenchmarkMeasure] + * + * Note: Only enabled on 'editorDev' build variant. + */ +fun endBenchmarkMeasure(label: String) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + val startTime = startBenchmarkFrom[label] ?: return + val total = SystemClock.elapsedRealtime() - startTime + benchmarkTracker[label] = total / 1000.0 + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Trace.endAsyncSection(label, 0) + } +} + +/** + * Dump the benchmark measurements. + * If [filepath] is valid, the data is also written in json format to the specified file. + * + * Note: Only enabled on 'editorDev' build variant. + */ +@JvmOverloads +fun dumpBenchmark(fileAccessHandler: FileAccessHandler?, filepath: String? = benchmarkFile) { + if (BuildConfig.FLAVOR != "editor" || BuildConfig.BUILD_TYPE != "dev") { + return + } + if (!useBenchmark) { + return + } + + val printOut = + benchmarkTracker.map { "\t- ${it.key} : ${it.value} sec." }.joinToString("\n") + Log.i(TAG, "BENCHMARK:\n$printOut") + + if (fileAccessHandler != null && !filepath.isNullOrBlank()) { + val fileId = fileAccessHandler.fileOpen(filepath, FileAccessFlags.WRITE) + if (fileId != FileAccessHandler.INVALID_FILE_ID) { + val jsonOutput = JSONObject(benchmarkTracker.toMap()).toString(4) + fileAccessHandler.fileWrite(fileId, ByteBuffer.wrap(jsonOutput.toByteArray())) + fileAccessHandler.fileClose(fileId) + } + } +} diff --git a/platform/android/java_godot_view_wrapper.cpp b/platform/android/java_godot_view_wrapper.cpp index 249a82bf5b65..c25171b5840d 100644 --- a/platform/android/java_godot_view_wrapper.cpp +++ b/platform/android/java_godot_view_wrapper.cpp @@ -40,6 +40,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { int android_device_api_level = android_get_device_api_level(); if (android_device_api_level >= __ANDROID_API_N__) { + _configure_pointer_icon = env->GetMethodID(_cls, "configurePointerIcon", "(ILjava/lang/String;FF)V"); _set_pointer_icon = env->GetMethodID(_cls, "setPointerIcon", "(I)V"); } if (android_device_api_level >= __ANDROID_API_O__) { @@ -49,7 +50,7 @@ GodotJavaViewWrapper::GodotJavaViewWrapper(jobject godot_view) { } bool GodotJavaViewWrapper::can_update_pointer_icon() const { - return _set_pointer_icon != nullptr; + return _configure_pointer_icon != nullptr && _set_pointer_icon != nullptr; } bool GodotJavaViewWrapper::can_capture_pointer() const { @@ -74,6 +75,16 @@ void GodotJavaViewWrapper::release_pointer_capture() { } } +void GodotJavaViewWrapper::configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot) { + if (_configure_pointer_icon != nullptr) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + jstring jImagePath = env->NewStringUTF(image_path.utf8().get_data()); + env->CallVoidMethod(_godot_view, _configure_pointer_icon, pointer_type, jImagePath, p_hotspot.x, p_hotspot.y); + } +} + void GodotJavaViewWrapper::set_pointer_icon(int pointer_type) { if (_set_pointer_icon != nullptr) { JNIEnv *env = get_jni_env(); diff --git a/platform/android/java_godot_view_wrapper.h b/platform/android/java_godot_view_wrapper.h index de71eddaeb89..e6bacdf9159a 100644 --- a/platform/android/java_godot_view_wrapper.h +++ b/platform/android/java_godot_view_wrapper.h @@ -31,6 +31,7 @@ #ifndef JAVA_GODOT_VIEW_WRAPPER_H #define JAVA_GODOT_VIEW_WRAPPER_H +#include "core/math/vector2.h" #include #include @@ -44,6 +45,8 @@ class GodotJavaViewWrapper { jmethodID _request_pointer_capture = 0; jmethodID _release_pointer_capture = 0; + + jmethodID _configure_pointer_icon = 0; jmethodID _set_pointer_icon = 0; public: @@ -54,6 +57,8 @@ class GodotJavaViewWrapper { void request_pointer_capture(); void release_pointer_capture(); + + void configure_pointer_icon(int pointer_type, const String &image_path, const Vector2 &p_hotspot); void set_pointer_icon(int pointer_type); ~GodotJavaViewWrapper(); diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 9d845835c407..c04dc6785add 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -82,6 +82,9 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _on_godot_main_loop_started = p_env->GetMethodID(godot_class, "onGodotMainLoopStarted", "()V"); _create_new_godot_instance = p_env->GetMethodID(godot_class, "createNewGodotInstance", "([Ljava/lang/String;)I"); _get_render_view = p_env->GetMethodID(godot_class, "getRenderView", "()Lorg/godotengine/godot/GodotView;"); + _begin_benchmark_measure = p_env->GetMethodID(godot_class, "beginBenchmarkMeasure", "(Ljava/lang/String;)V"); + _end_benchmark_measure = p_env->GetMethodID(godot_class, "endBenchmarkMeasure", "(Ljava/lang/String;)V"); + _dump_benchmark = p_env->GetMethodID(godot_class, "dumpBenchmark", "(Ljava/lang/String;)V"); // get some Activity method pointers... _get_class_loader = p_env->GetMethodID(activity_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); @@ -386,3 +389,30 @@ int GodotJavaWrapper::create_new_godot_instance(List args) { return 0; } } + +void GodotJavaWrapper::begin_benchmark_measure(const String &p_label) { + if (_begin_benchmark_measure) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); + env->CallVoidMethod(godot_instance, _begin_benchmark_measure, j_label); + } +} + +void GodotJavaWrapper::end_benchmark_measure(const String &p_label) { + if (_end_benchmark_measure) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_label = env->NewStringUTF(p_label.utf8().get_data()); + env->CallVoidMethod(godot_instance, _end_benchmark_measure, j_label); + } +} + +void GodotJavaWrapper::dump_benchmark(const String &benchmark_file) { + if (_dump_benchmark) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + jstring j_benchmark_file = env->NewStringUTF(benchmark_file.utf8().get_data()); + env->CallVoidMethod(godot_instance, _dump_benchmark, j_benchmark_file); + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 5cdde5c12501..5e49074f7627 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -76,6 +76,9 @@ class GodotJavaWrapper { jmethodID _get_class_loader = nullptr; jmethodID _create_new_godot_instance = nullptr; jmethodID _get_render_view = nullptr; + jmethodID _begin_benchmark_measure = nullptr; + jmethodID _end_benchmark_measure = nullptr; + jmethodID _dump_benchmark = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -113,6 +116,9 @@ class GodotJavaWrapper { void vibrate(int p_duration_ms); String get_input_fallback_mapping(); int create_new_godot_instance(List args); + void begin_benchmark_measure(const String &p_label); + void end_benchmark_measure(const String &p_label); + void dump_benchmark(const String &benchmark_file); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/android/os_android.cpp b/platform/android/os_android.cpp index 5b1ed9dc284f..223cdcd020d6 100644 --- a/platform/android/os_android.cpp +++ b/platform/android/os_android.cpp @@ -302,11 +302,11 @@ OS::MouseMode OS_Android::get_mouse_mode() const { return mouse_mode; } -void OS_Android::set_cursor_shape(CursorShape p_shape) { +void OS_Android::_set_cursor_shape_helper(CursorShape p_shape, bool force) { if (!godot_java->get_godot_view()->can_update_pointer_icon()) { return; } - if (cursor_shape == p_shape) { + if (cursor_shape == p_shape && !force) { return; } @@ -316,6 +316,19 @@ void OS_Android::set_cursor_shape(CursorShape p_shape) { } } +void OS_Android::set_cursor_shape(CursorShape p_shape) { + _set_cursor_shape_helper(p_shape); +} + +void OS_Android::set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot) { + String cursor_path = p_cursor.is_valid() ? p_cursor->get_path() : ""; + if (!cursor_path.empty()) { + cursor_path = ProjectSettings::get_singleton()->globalize_path(cursor_path); + } + godot_java->get_godot_view()->configure_pointer_icon(android_cursors[cursor_shape], cursor_path, p_hotspot); + _set_cursor_shape_helper(p_shape, true); +} + OS::CursorShape OS_Android::get_cursor_shape() const { return cursor_shape; } @@ -669,6 +682,27 @@ String OS_Android::get_config_path() const { return get_user_data_dir().plus_file("config"); } +void OS_Android::benchmark_begin_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + godot_java->begin_benchmark_measure(p_what); +#endif +} + +void OS_Android::benchmark_end_measure(const String &p_what) { +#ifdef TOOLS_ENABLED + godot_java->end_benchmark_measure(p_what); +#endif +} + +void OS_Android::benchmark_dump() { +#ifdef TOOLS_ENABLED + if (!is_use_benchmark_set()) { + return; + } + godot_java->dump_benchmark(get_benchmark_file()); +#endif +} + bool OS_Android::_check_internal_feature_support(const String &p_feature) { if (p_feature == "mobile") { //TODO support etc2 only if GLES3 driver is selected diff --git a/platform/android/os_android.h b/platform/android/os_android.h index 41bb956eed1c..5bb2a5571092 100644 --- a/platform/android/os_android.h +++ b/platform/android/os_android.h @@ -163,8 +163,11 @@ class OS_Android : public OS_Unix { virtual void hide_virtual_keyboard(); virtual int get_virtual_keyboard_height() const; + void _set_cursor_shape_helper(CursorShape p_shape, bool force = false); virtual void set_cursor_shape(CursorShape p_shape); virtual CursorShape get_cursor_shape() const; + virtual void set_custom_mouse_cursor(const RES &p_cursor, CursorShape p_shape, const Vector2 &p_hotspot); + virtual void set_mouse_mode(MouseMode p_mode); virtual MouseMode get_mouse_mode() const; @@ -217,6 +220,10 @@ class OS_Android : public OS_Unix { virtual Error execute(const String &p_path, const List &p_arguments, bool p_blocking = true, ProcessID *r_child_id = nullptr, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false); virtual Error kill(const ProcessID &p_pid); + virtual void benchmark_begin_measure(const String &p_what); + virtual void benchmark_end_measure(const String &p_what); + virtual void benchmark_dump(); + virtual bool _check_internal_feature_support(const String &p_feature); OS_Android(GodotJavaWrapper *p_godot_java, GodotIOJavaWrapper *p_godot_io_java, bool p_use_apk_expansion); ~OS_Android(); diff --git a/scene/2d/visibility_notifier_2d.cpp b/scene/2d/visibility_notifier_2d.cpp index a6800843aa13..53d91132e112 100644 --- a/scene/2d/visibility_notifier_2d.cpp +++ b/scene/2d/visibility_notifier_2d.cpp @@ -168,6 +168,13 @@ void VisibilityEnabler2D::_screen_enter() { if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) { get_parent()->set_process(true); } + if (enabler[ENABLER_PARENT_VISIBILITY] && get_parent()) { + CanvasItem *ci = Object::cast_to(get_parent()); + + if (ci) { + ci->set_visible(true); + } + } visible = true; } @@ -183,6 +190,13 @@ void VisibilityEnabler2D::_screen_exit() { if (enabler[ENABLER_PARENT_PROCESS] && get_parent()) { get_parent()->set_process(false); } + if (enabler[ENABLER_PARENT_VISIBILITY] && get_parent()) { + CanvasItem *ci = Object::cast_to(get_parent()); + + if (ci) { + ci->set_visible(false); + } + } visible = false; } @@ -265,6 +279,14 @@ void VisibilityEnabler2D::_notification(int p_what) { get_parent()->connect(SceneStringNames::get_singleton()->ready, get_parent(), "set_process", varray(false), CONNECT_REFERENCE_COUNTED); } + if (enabler[ENABLER_PARENT_VISIBILITY] && get_parent()) { + CanvasItem *ci = Object::cast_to(get_parent()); + + if (ci) { + ci->connect(SceneStringNames::get_singleton()->ready, + ci, "set_visible", varray(false), CONNECT_REFERENCE_COUNTED); + } + } } if (p_what == NOTIFICATION_EXIT_TREE) { @@ -355,6 +377,7 @@ void VisibilityEnabler2D::_bind_methods() { ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "pause_animated_sprites"), "set_enabler", "is_enabler_enabled", ENABLER_PAUSE_ANIMATED_SPRITES); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PROCESS); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "physics_process_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_PHYSICS_PROCESS); + ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "visibility_parent"), "set_enabler", "is_enabler_enabled", ENABLER_PARENT_VISIBILITY); BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATIONS); BIND_ENUM_CONSTANT(ENABLER_FREEZE_BODIES); @@ -362,6 +385,7 @@ void VisibilityEnabler2D::_bind_methods() { BIND_ENUM_CONSTANT(ENABLER_PARENT_PROCESS); BIND_ENUM_CONSTANT(ENABLER_PARENT_PHYSICS_PROCESS); BIND_ENUM_CONSTANT(ENABLER_PAUSE_ANIMATED_SPRITES); + BIND_ENUM_CONSTANT(ENABLER_PARENT_VISIBILITY); BIND_ENUM_CONSTANT(ENABLER_MAX); } diff --git a/scene/2d/visibility_notifier_2d.h b/scene/2d/visibility_notifier_2d.h index 6ca67bfb3fa4..ba6cbebc1a81 100644 --- a/scene/2d/visibility_notifier_2d.h +++ b/scene/2d/visibility_notifier_2d.h @@ -78,6 +78,7 @@ class VisibilityEnabler2D : public VisibilityNotifier2D { ENABLER_PARENT_PROCESS, ENABLER_PARENT_PHYSICS_PROCESS, ENABLER_PAUSE_ANIMATED_SPRITES, + ENABLER_PARENT_VISIBILITY, ENABLER_MAX }; diff --git a/scene/gui/tabs.cpp b/scene/gui/tabs.cpp index 3ab4b33387e8..b32f25325694 100644 --- a/scene/gui/tabs.cpp +++ b/scene/gui/tabs.cpp @@ -193,12 +193,14 @@ void Tabs::_gui_input(const Ref &p_event) { for (int i = offset; i <= max_drawn_tab; i++) { if (tabs[i].rb_rect.has_point(pos)) { rb_pressing = true; + _update_hover(); update(); return; } if (tabs[i].cb_rect.has_point(pos) && (cb_displaypolicy == CLOSE_BUTTON_SHOW_ALWAYS || (cb_displaypolicy == CLOSE_BUTTON_SHOW_ACTIVE_ONLY && i == current))) { cb_pressing = true; + _update_hover(); update(); return; } diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp index db362bd7c73f..87272dab1a69 100644 --- a/scene/main/canvas_layer.cpp +++ b/scene/main/canvas_layer.cpp @@ -35,7 +35,11 @@ void CanvasLayer::set_layer(int p_xform) { layer = p_xform; if (viewport.is_valid()) { - VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_position_in_parent()); + // For the sublayer, we will use the order in which the layer occurs in the scene tree + // rather than just the child_id via get_position_in_parent(). + // We have 32 bits to play with for the sublayer (or more likely 31, as sublayer is signed) + // (see Viewport::CanvasKey constructor in visual_server_viewport.h). + VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, _layer_order_in_tree); vp->_gui_set_root_order_dirty(); } } @@ -59,6 +63,34 @@ void CanvasLayer::set_visible(bool p_visible) { } } +// Make sure layer orders are up to date whenever moving layers in the tree. +void CanvasLayer::_update_layer_orders() { + if (is_inside_tree() && get_tree()) { + Node *root = get_tree()->get_root(); + if (root) { + uint32_t layer_order_count = 0; + _calculate_layer_orders_in_tree(root, layer_order_count); + } + } +} + +// When rendering layers, if the layer id (set by user) and sublayer (child id) is the same +// we need something else sensible which we can sort repeatably to determine which layers should render first, +// so we will simply calculate the order that the layers occur throughout the scene tree. +void CanvasLayer::_calculate_layer_orders_in_tree(Node *p_node, uint32_t &r_order) { + CanvasLayer *layer = Object::cast_to(p_node); + if (layer) { + layer->_layer_order_in_tree = r_order++; + + // Force an update of the layer order in the VisualServer + set_layer(get_layer()); + } + + for (int n = 0; n < p_node->get_child_count(); n++) { + _calculate_layer_orders_in_tree(p_node->get_child(n), r_order); + } +} + void CanvasLayer::show() { set_visible(true); } @@ -183,25 +215,24 @@ void CanvasLayer::_notification(int p_what) { viewport = vp->get_viewport_rid(); VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas); - VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_position_in_parent()); VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); _update_follow_viewport(); - + _update_layer_orders(); } break; case NOTIFICATION_EXIT_TREE: { ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized."); + _update_layer_orders(); vp->_canvas_layer_remove(this); VisualServer::get_singleton()->viewport_remove_canvas(viewport, canvas); viewport = RID(); _update_follow_viewport(false); - } break; case NOTIFICATION_MOVED_IN_PARENT: { - if (is_inside_tree()) { - VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_position_in_parent()); - } - + // Note: As this step requires traversing the entire scene tree, it is thus expensive + // to move the canvas layer multiple times. Take special care when deleting / moving + // multiple nodes to prevent multiple NOTIFICATION_MOVED_IN_PARENT occurring. + _update_layer_orders(); } break; } } @@ -248,8 +279,8 @@ void CanvasLayer::set_custom_viewport(Node *p_viewport) { viewport = vp->get_viewport_rid(); VisualServer::get_singleton()->viewport_attach_canvas(viewport, canvas); - VisualServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_position_in_parent()); VisualServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform); + _update_layer_orders(); } } @@ -380,6 +411,7 @@ CanvasLayer::CanvasLayer() { follow_viewport = false; follow_viewport_scale = 1.0; _set_observe_notification_moved_in_parent(true); + _layer_order_in_tree = 0; } CanvasLayer::~CanvasLayer() { diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h index 90bdcd7a78b8..47ecfebdc91f 100644 --- a/scene/main/canvas_layer.h +++ b/scene/main/canvas_layer.h @@ -45,6 +45,7 @@ class CanvasLayer : public Node { int layer; Transform2D transform; RID canvas; + uint32_t _layer_order_in_tree; ObjectID custom_viewport_id; // to check validity Viewport *custom_viewport; @@ -61,6 +62,8 @@ class CanvasLayer : public Node { void _update_xform(); void _update_locrotscale(); void _update_follow_viewport(bool p_force_exit = false); + void _calculate_layer_orders_in_tree(Node *p_node, uint32_t &r_order); + void _update_layer_orders(); protected: void _notification(int p_what); diff --git a/servers/physics/body_pair_sw.cpp b/servers/physics/body_pair_sw.cpp index f0cc8ae68118..cd07a783667d 100644 --- a/servers/physics/body_pair_sw.cpp +++ b/servers/physics/body_pair_sw.cpp @@ -34,18 +34,6 @@ #include "core/os/os.h" #include "space_sw.h" -/* -#define NO_ACCUMULATE_IMPULSES -#define NO_SPLIT_IMPULSES - -#define NO_FRICTION -*/ - -#define NO_TANGENTIALS -/* BODY PAIR */ - -//#define ALLOWED_PENETRATION 0.01 -#define RELAXATION_TIMESTEPS 3 #define MIN_VELOCITY 0.0001 #define MAX_BIAS_ROTATION (Math_PI / 8) @@ -335,9 +323,9 @@ bool BodyPairSW::setup(real_t p_step) { c.bounce = combine_bounce(A, B); if (c.bounce) { - Vector3 crA = A->get_angular_velocity().cross(c.rA); - Vector3 crB = B->get_angular_velocity().cross(c.rB); - Vector3 dv = B->get_linear_velocity() + crB - A->get_linear_velocity() - crA; + Vector3 crA = A->get_prev_angular_velocity().cross(c.rA); + Vector3 crB = B->get_prev_angular_velocity().cross(c.rB); + Vector3 dv = B->get_prev_linear_velocity() + crB - A->get_prev_linear_velocity() - crA; //normal impule c.bounce = c.bounce * dv.dot(c.normal); } diff --git a/servers/physics/body_sw.cpp b/servers/physics/body_sw.cpp index e52cd13998c7..601f6fdd73fe 100644 --- a/servers/physics/body_sw.cpp +++ b/servers/physics/body_sw.cpp @@ -509,6 +509,9 @@ void BodySW::integrate_forces(real_t p_step) { area_linear_damp=damp_area->get_linear_damp(); */ + prev_linear_velocity = linear_velocity; + prev_angular_velocity = angular_velocity; + Vector3 motion; bool do_motion = false; diff --git a/servers/physics/body_sw.h b/servers/physics/body_sw.h index 42eb7e263e25..a3ba2849efaf 100644 --- a/servers/physics/body_sw.h +++ b/servers/physics/body_sw.h @@ -44,6 +44,9 @@ class BodySW : public CollisionObjectSW { Vector3 linear_velocity; Vector3 angular_velocity; + Vector3 prev_linear_velocity; + Vector3 prev_angular_velocity; + Vector3 biased_linear_velocity; Vector3 biased_angular_velocity; real_t mass; @@ -215,6 +218,9 @@ class BodySW : public CollisionObjectSW { _FORCE_INLINE_ void set_angular_velocity(const Vector3 &p_velocity) { angular_velocity = p_velocity; } _FORCE_INLINE_ Vector3 get_angular_velocity() const { return angular_velocity; } + _FORCE_INLINE_ Vector3 get_prev_linear_velocity() const { return prev_linear_velocity; } + _FORCE_INLINE_ Vector3 get_prev_angular_velocity() const { return prev_angular_velocity; } + _FORCE_INLINE_ const Vector3 &get_biased_linear_velocity() const { return biased_linear_velocity; } _FORCE_INLINE_ const Vector3 &get_biased_angular_velocity() const { return biased_angular_velocity; } diff --git a/servers/physics_2d/body_2d_sw.cpp b/servers/physics_2d/body_2d_sw.cpp index 28de7927189d..556ba9c2a8cf 100644 --- a/servers/physics_2d/body_2d_sw.cpp +++ b/servers/physics_2d/body_2d_sw.cpp @@ -461,6 +461,9 @@ void Body2DSW::integrate_forces(real_t p_step) { area_linear_damp=damp_area->get_linear_damp(); */ + prev_linear_velocity = linear_velocity; + prev_angular_velocity = angular_velocity; + Vector2 motion; bool do_motion = false; diff --git a/servers/physics_2d/body_2d_sw.h b/servers/physics_2d/body_2d_sw.h index 7bd301936ffc..e0c4e31d5c74 100644 --- a/servers/physics_2d/body_2d_sw.h +++ b/servers/physics_2d/body_2d_sw.h @@ -47,6 +47,9 @@ class Body2DSW : public CollisionObject2DSW { Vector2 linear_velocity; real_t angular_velocity; + Vector2 prev_linear_velocity; + real_t prev_angular_velocity = 0.0; + real_t linear_damp; real_t angular_damp; real_t gravity_scale; @@ -195,6 +198,9 @@ class Body2DSW : public CollisionObject2DSW { _FORCE_INLINE_ void set_angular_velocity(real_t p_velocity) { angular_velocity = p_velocity; } _FORCE_INLINE_ real_t get_angular_velocity() const { return angular_velocity; } + _FORCE_INLINE_ Vector2 get_prev_linear_velocity() const { return prev_linear_velocity; } + _FORCE_INLINE_ real_t get_prev_angular_velocity() const { return prev_angular_velocity; } + _FORCE_INLINE_ void set_biased_linear_velocity(const Vector2 &p_velocity) { biased_linear_velocity = p_velocity; } _FORCE_INLINE_ Vector2 get_biased_linear_velocity() const { return biased_linear_velocity; } diff --git a/servers/physics_2d/body_pair_2d_sw.cpp b/servers/physics_2d/body_pair_2d_sw.cpp index 61488e924380..cebb87aa6b07 100644 --- a/servers/physics_2d/body_pair_2d_sw.cpp +++ b/servers/physics_2d/body_pair_2d_sw.cpp @@ -32,7 +32,6 @@ #include "collision_solver_2d_sw.h" #include "space_2d_sw.h" -#define POSITION_CORRECTION #define ACCUMULATE_IMPULSES void BodyPair2DSW::_add_contact(const Vector2 &p_point_A, const Vector2 &p_point_B, void *p_self) { @@ -423,9 +422,9 @@ bool BodyPair2DSW::setup(real_t p_step) { c.bounce = combine_bounce(A, B); if (c.bounce) { - Vector2 crA(-A->get_angular_velocity() * c.rA.y, A->get_angular_velocity() * c.rA.x); - Vector2 crB(-B->get_angular_velocity() * c.rB.y, B->get_angular_velocity() * c.rB.x); - Vector2 dv = B->get_linear_velocity() + crB - A->get_linear_velocity() - crA; + Vector2 crA(-A->get_prev_angular_velocity() * c.rA.y, A->get_prev_angular_velocity() * c.rA.x); + Vector2 crB(-B->get_prev_angular_velocity() * c.rB.y, B->get_prev_angular_velocity() * c.rB.x); + Vector2 dv = B->get_prev_linear_velocity() + crB - A->get_prev_linear_velocity() - crA; c.bounce = c.bounce * dv.dot(c.normal); } diff --git a/servers/visual/visual_server_canvas.cpp b/servers/visual/visual_server_canvas.cpp index 81637021e94c..89898aa9c706 100644 --- a/servers/visual/visual_server_canvas.cpp +++ b/servers/visual/visual_server_canvas.cpp @@ -1476,32 +1476,6 @@ Rect2 VisualServerCanvas::_debug_canvas_item_get_local_bound(RID p_item) { return canvas_item->local_bound; } -void VisualServerCanvas::canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform) { - Item *canvas_item = canvas_item_owner.getornull(p_item); - ERR_FAIL_COND(!canvas_item); - - if (!canvas_item->skinning_data) { - canvas_item->skinning_data = memnew(Item::SkinningData); - } - canvas_item->skinning_data->skeleton_relative_xform = p_relative_xform; - canvas_item->skinning_data->skeleton_relative_xform_inv = p_relative_xform.affine_inverse(); - - // Set any Polygon2Ds pre-calced bone bounds to dirty. - for (int n = 0; n < canvas_item->commands.size(); n++) { - Item::Command *c = canvas_item->commands[n]; - if (c->type == Item::Command::TYPE_POLYGON) { - Item::CommandPolygon *polygon = static_cast(c); - - // Make sure skinning data is present. - if (!polygon->skinning_data) { - polygon->skinning_data = memnew(Item::CommandPolygon::SkinningData); - } - - polygon->skinning_data->dirty = true; - } - } -} - // Useful especially for origin shifting. void VisualServerCanvas::canvas_item_transform_physics_interpolation(RID p_item, Transform2D p_transform) { Item *canvas_item = canvas_item_owner.getornull(p_item); @@ -1560,6 +1534,32 @@ void VisualServerCanvas::canvas_light_occluder_transform_physics_interpolation(R occluder->xform_curr = p_transform * occluder->xform_curr; } +void VisualServerCanvas::canvas_item_set_skeleton_relative_xform(RID p_item, Transform2D p_relative_xform) { + Item *canvas_item = canvas_item_owner.getornull(p_item); + ERR_FAIL_COND(!canvas_item); + + if (!canvas_item->skinning_data) { + canvas_item->skinning_data = memnew(Item::SkinningData); + } + canvas_item->skinning_data->skeleton_relative_xform = p_relative_xform; + canvas_item->skinning_data->skeleton_relative_xform_inv = p_relative_xform.affine_inverse(); + + // Set any Polygon2Ds pre-calced bone bounds to dirty. + for (int n = 0; n < canvas_item->commands.size(); n++) { + Item::Command *c = canvas_item->commands[n]; + if (c->type == Item::Command::TYPE_POLYGON) { + Item::CommandPolygon *polygon = static_cast(c); + + // Make sure skinning data is present. + if (!polygon->skinning_data) { + polygon->skinning_data = memnew(Item::CommandPolygon::SkinningData); + } + + polygon->skinning_data->dirty = true; + } + } +} + void VisualServerCanvas::canvas_item_attach_skeleton(RID p_item, RID p_skeleton) { Item *canvas_item = canvas_item_owner.getornull(p_item); ERR_FAIL_COND(!canvas_item);