diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 21a9014626db..9dca47a0b4f2 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() { Error err = decode_variant(var, buf, in_pos, &read); ERR_CONTINUE(read != in_pos || err != OK); ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array."); - mutex.lock(); + MutexLock lock(mutex); in_queue.push_back(var); - mutex.unlock(); } } } diff --git a/core/io/resource.cpp b/core/io/resource.cpp index f2071ebe0d27..ef612cfa71e2 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) { p_take_over = false; // Can't take over an empty path } - ResourceCache::lock.lock(); + { + MutexLock lock(ResourceCache::lock); - if (!path_cache.is_empty()) { - ResourceCache::resources.erase(path_cache); - } + if (!path_cache.is_empty()) { + ResourceCache::resources.erase(path_cache); + } - path_cache = ""; + path_cache = ""; - Ref existing = ResourceCache::get_ref(p_path); + Ref existing = ResourceCache::get_ref(p_path); - if (existing.is_valid()) { - if (p_take_over) { - existing->path_cache = String(); - ResourceCache::resources.erase(p_path); - } else { - ResourceCache::lock.unlock(); - ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + if (existing.is_valid()) { + if (p_take_over) { + existing->path_cache = String(); + ResourceCache::resources.erase(p_path); + } else { + ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + } } - } - path_cache = p_path; + path_cache = p_path; - if (!path_cache.is_empty()) { - ResourceCache::resources[path_cache] = this; + if (!path_cache.is_empty()) { + ResourceCache::resources[path_cache] = this; + } } - ResourceCache::lock.unlock(); _resource_path_changed(); } @@ -492,15 +492,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); if (p_remapped) { ResourceLoader::remapped_list.add(&remapped_list); } else { ResourceLoader::remapped_list.remove(&remapped_list); } - - ResourceCache::lock.unlock(); } #ifdef TOOLS_ENABLED @@ -573,14 +571,13 @@ Resource::~Resource() { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); // Only unregister from the cache if this is the actual resource listed there. // (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.) HashMap::Iterator E = ResourceCache::resources.find(path_cache); if (likely(E && E->value == this)) { ResourceCache::resources.remove(E); } - ResourceCache::lock.unlock(); } HashMap ResourceCache::resources; @@ -609,18 +606,20 @@ void ResourceCache::clear() { } bool ResourceCache::has(const String &p_path) { - lock.lock(); + Resource **res = nullptr; - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); - if (res && (*res)->get_reference_count() == 0) { - // This resource is in the process of being deleted, ignore its existence. - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; - } + res = resources.getptr(p_path); - lock.unlock(); + if (res && (*res)->get_reference_count() == 0) { + // This resource is in the process of being deleted, ignore its existence. + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } + } if (!res) { return false; @@ -631,28 +630,27 @@ bool ResourceCache::has(const String &p_path) { Ref ResourceCache::get_ref(const String &p_path) { Ref ref; - lock.lock(); - - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); + Resource **res = resources.getptr(p_path); - if (res) { - ref = Ref(*res); - } + if (res) { + ref = Ref(*res); + } - if (res && !ref.is_valid()) { - // This resource is in the process of being deleted, ignore its existence - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; + if (res && !ref.is_valid()) { + // This resource is in the process of being deleted, ignore its existence + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } } - lock.unlock(); - return ref; } void ResourceCache::get_cached_resources(List> *p_resources) { - lock.lock(); + MutexLock mutex_lock(lock); LocalVector to_remove; @@ -672,14 +670,9 @@ void ResourceCache::get_cached_resources(List> *p_resources) { for (const String &E : to_remove) { resources.erase(E); } - - lock.unlock(); } int ResourceCache::get_cached_resource_count() { - lock.lock(); - int rc = resources.size(); - lock.unlock(); - - return rc; + MutexLock mutex_lock(lock); + return resources.size(); } diff --git a/core/object/object.cpp b/core/object/object.cpp index 3f5378b7f682..ee79e69921c5 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -1904,7 +1904,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) { void *binding = nullptr; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { binding = _instance_bindings[i].binding; @@ -1935,14 +1935,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi _instance_binding_count++; } - _instance_binding_mutex.unlock(); - return binding; } bool Object::has_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { found = true; @@ -1950,14 +1948,12 @@ bool Object::has_instance_binding(void *p_token) { } } - _instance_binding_mutex.unlock(); - return found; } void Object::free_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (!found && _instance_bindings[i].token == p_token) { if (_instance_bindings[i].free_callback) { @@ -1976,7 +1972,6 @@ void Object::free_instance_binding(void *p_token) { if (found) { _instance_binding_count--; } - _instance_binding_mutex.unlock(); } #ifdef TOOLS_ENABLED diff --git a/core/object/object.h b/core/object/object.h index adb50268d2ad..dcddfc6b685b 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -680,7 +680,7 @@ class Object { _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { bool can_die = true; if (_instance_bindings) { - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].reference_callback) { if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) { @@ -688,7 +688,6 @@ class Object { } } } - _instance_binding_mutex.unlock(); } return can_die; } diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 25ad3bf96412..36a3f9d99b4a 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) { if (finished_users == max_users) { // Get rid of the group, because nobody else is using it. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(p_task->group); - task_mutex.unlock(); } // For groups, tasks get rid of themselves. @@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo } bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Task *const *taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task } - bool completed = (*taskp)->completed; - task_mutex.unlock(); - - return completed; + return (*taskp)->completed; } Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { @@ -522,10 +517,9 @@ void WorkerThreadPool::yield() { } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { - task_mutex.lock(); + MutexLock task_lock(task_mutex); Task **taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; @@ -534,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { // This avoids a race condition where a task is created and yield-over called before it's processed. task->pending_notify_yield_over = true; } - task_mutex.unlock(); return; } @@ -542,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { td.yield_is_over = true; td.signaled = true; td.cond_var.notify_one(); - - task_mutex.unlock(); } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { @@ -601,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act } uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(0, "Invalid Group ID"); } - uint32_t elements = (*groupp)->completed_index.get(); - task_mutex.unlock(); - return elements; + return (*groupp)->completed_index.get(); } bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Group ID"); } - bool completed = (*groupp)->completed.is_set(); - task_mutex.unlock(); - return completed; + return (*groupp)->completed.is_set(); } void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { @@ -644,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { if (finished_users == max_users) { // All tasks using this group are gone (finished before the group), so clear the group too. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(group); - task_mutex.unlock(); } } - task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. + MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. groups.erase(p_group); - task_mutex.unlock(); #endif } diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 5d59d65f92ff..0294dbfbbcfd 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -39,6 +39,30 @@ StaticCString StaticCString::create(const char *p_ptr) { return scs; } +bool StringName::_Data::operator==(const String &p_name) const { + if (cname) { + return p_name == cname; + } else { + return name == p_name; + } +} + +bool StringName::_Data::operator!=(const String &p_name) const { + return !operator==(p_name); +} + +bool StringName::_Data::operator==(const char *p_name) const { + if (cname) { + return strcmp(cname, p_name) == 0; + } else { + return name == p_name; + } +} + +bool StringName::_Data::operator!=(const char *p_name) const { + return !operator==(p_name); +} + StringName _scs_create(const char *p_chr, bool p_static) { return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName()); } @@ -139,19 +163,19 @@ void StringName::unref() { } bool StringName::operator==(const String &p_name) const { - if (!_data) { - return (p_name.length() == 0); + if (_data) { + return _data->operator==(p_name); } - return (_data->get_name() == p_name); + return p_name.is_empty(); } bool StringName::operator==(const char *p_name) const { - if (!_data) { - return (p_name[0] == 0); + if (_data) { + return _data->operator==(p_name); } - return (_data->get_name() == p_name); + return p_name[0] == 0; } bool StringName::operator!=(const String &p_name) const { @@ -168,9 +192,47 @@ bool StringName::operator!=(const StringName &p_name) const { return _data != p_name._data; } -void StringName::operator=(const StringName &p_name) { +char32_t StringName::operator[](int p_index) const { + if (_data) { + if (_data->cname) { + CRASH_BAD_INDEX(p_index, static_cast(strlen(_data->cname))); + return _data->cname[p_index]; + } else { + return _data->name[p_index]; + } + } + + CRASH_BAD_INDEX(p_index, 0); + return 0; +} + +int StringName::length() const { + if (_data) { + if (_data->cname) { + return strlen(_data->cname); + } else { + return _data->name.length(); + } + } + + return 0; +} + +bool StringName::is_empty() const { + if (_data) { + if (_data->cname) { + return _data->cname[0] == 0; + } else { + return _data->name.is_empty(); + } + } + + return true; +} + +StringName &StringName::operator=(const StringName &p_name) { if (this == &p_name) { - return; + return *this; } unref(); @@ -178,6 +240,8 @@ void StringName::operator=(const StringName &p_name) { if (p_name._data && p_name._data->refcount.ref()) { _data = p_name._data; } + + return *this; } StringName::StringName(const StringName &p_name) { @@ -191,11 +255,10 @@ StringName::StringName(const StringName &p_name) { } void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) { - mutex.lock(); + MutexLock lock(mutex); if (*ptr == StringName()) { *ptr = StringName(p_name, true); } - mutex.unlock(); } StringName::StringName(const char *p_name, bool p_static) { @@ -217,7 +280,7 @@ StringName::StringName(const char *p_name, bool p_static) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -276,7 +339,7 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_static_string.ptr) { + if (_data->hash == hash && _data->operator==(p_static_string.ptr)) { break; } _data = _data->next; @@ -334,7 +397,7 @@ StringName::StringName(const String &p_name, bool p_static) { _data = _table[idx]; while (_data) { - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -393,7 +456,7 @@ StringName StringName::search(const char *p_name) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -430,7 +493,7 @@ StringName StringName::search(const char32_t *p_name) { while (_data) { // compare hash first - if (_data->hash == hash && _data->get_name() == p_name) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -456,7 +519,7 @@ StringName StringName::search(const String &p_name) { while (_data) { // compare hash first - if (_data->hash == hash && p_name == _data->get_name()) { + if (_data->hash == hash && _data->operator==(p_name)) { break; } _data = _data->next; @@ -475,15 +538,15 @@ StringName StringName::search(const String &p_name) { } bool operator==(const String &p_name, const StringName &p_string_name) { - return p_name == p_string_name.operator String(); + return p_string_name.operator==(p_name); } bool operator!=(const String &p_name, const StringName &p_string_name) { - return p_name != p_string_name.operator String(); + return p_string_name.operator!=(p_name); } bool operator==(const char *p_name, const StringName &p_string_name) { - return p_name == p_string_name.operator String(); + return p_string_name.operator==(p_name); } bool operator!=(const char *p_name, const StringName &p_string_name) { - return p_name != p_string_name.operator String(); + return p_string_name.operator!=(p_name); } diff --git a/core/string/string_name.h b/core/string/string_name.h index 0eb98cf64b57..288e2c75209d 100644 --- a/core/string/string_name.h +++ b/core/string/string_name.h @@ -60,6 +60,11 @@ class StringName { uint32_t debug_references = 0; #endif String get_name() const { return cname ? String(cname) : name; } + bool operator==(const String &p_name) const; + bool operator!=(const String &p_name) const; + bool operator==(const char *p_name) const; + bool operator!=(const char *p_name) const; + int idx = 0; uint32_t hash = 0; _Data *prev = nullptr; @@ -99,6 +104,10 @@ class StringName { bool operator!=(const String &p_name) const; bool operator!=(const char *p_name) const; + char32_t operator[](int p_index) const; + int length() const; + bool is_empty() const; + _FORCE_INLINE_ bool is_node_unique_name() const { if (!_data) { return false; @@ -175,7 +184,7 @@ class StringName { } }; - void operator=(const StringName &p_name); + StringName &operator=(const StringName &p_name); StringName(const char *p_name, bool p_static = false); StringName(const StringName &p_name); StringName(const String &p_name, bool p_static = false); diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index bd46c54990fd..861a53aaad10 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -223,6 +223,13 @@ Returns the [Color] modulating the column's icon. + + + + + Returns the given column's icon overlay [Texture2D]. + + @@ -662,6 +669,14 @@ Modulates the given column's icon with [param modulate]. + + + + + + Sets the given cell's icon overlay [Texture2D]. The cell has to be in [constant CELL_MODE_ICON] mode, and icon has to be set. Overlay is drawn on top of icon, in the bottom left corner. + + diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index a4208829b7dd..e4bad8808374 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -397,12 +397,18 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) { } p_path = fix_path(p_path); + if (p_path.ends_with("/")) { + p_path = p_path.left(-1); + } if (p_new_path.is_relative_path()) { p_new_path = get_current_dir().path_join(p_new_path); } p_new_path = fix_path(p_new_path); + if (p_new_path.ends_with("/")) { + p_new_path = p_new_path.left(-1); + } return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED; } @@ -413,6 +419,9 @@ Error DirAccessUnix::remove(String p_path) { } p_path = fix_path(p_path); + if (p_path.ends_with("/")) { + p_path = p_path.left(-1); + } struct stat flags = {}; if ((stat(p_path.utf8().get_data(), &flags) != 0)) { @@ -432,6 +441,9 @@ bool DirAccessUnix::is_link(String p_file) { } p_file = fix_path(p_file); + if (p_file.ends_with("/")) { + p_file = p_file.left(-1); + } struct stat flags = {}; if ((lstat(p_file.utf8().get_data(), &flags) != 0)) { @@ -447,6 +459,9 @@ String DirAccessUnix::read_link(String p_file) { } p_file = fix_path(p_file); + if (p_file.ends_with("/")) { + p_file = p_file.left(-1); + } char buf[256]; memset(buf, 0, 256); diff --git a/editor/directory_create_dialog.cpp b/editor/directory_create_dialog.cpp index 604531f109d5..ee03d5a7f695 100644 --- a/editor/directory_create_dialog.cpp +++ b/editor/directory_create_dialog.cpp @@ -31,6 +31,7 @@ #include "directory_create_dialog.h" #include "core/io/dir_access.h" +#include "editor/editor_file_system.h" #include "editor/editor_node.h" #include "editor/gui/editor_validation_panel.h" #include "editor/themes/editor_scale.h" @@ -38,33 +39,48 @@ #include "scene/gui/label.h" #include "scene/gui/line_edit.h" -static String sanitize_input(const String &p_path) { +String DirectoryCreateDialog::_sanitize_input(const String &p_path) const { String path = p_path.strip_edges(); - if (path.ends_with("/")) { - path = path.left(path.length() - 1); + if (mode == MODE_DIRECTORY) { + path = path.trim_suffix("/"); } return path; } String DirectoryCreateDialog::_validate_path(const String &p_path) const { if (p_path.is_empty()) { - return TTR("Folder name cannot be empty."); + return TTR("Name cannot be empty."); } - - if (p_path.contains("\\") || p_path.contains(":") || p_path.contains("*") || - p_path.contains("|") || p_path.contains(">")) { - return TTR("Folder name contains invalid characters."); + if (mode == MODE_FILE && p_path.ends_with("/")) { + return TTR("File name can't end with /."); } - for (const String &part : p_path.split("/")) { + const PackedStringArray splits = p_path.split("/"); + for (int i = 0; i < splits.size(); i++) { + const String &part = splits[i]; + bool is_file = mode == MODE_FILE && i == splits.size() - 1; + if (part.is_empty()) { - return TTR("Folder name cannot be empty."); + if (is_file) { + return TTR("File name cannot be empty."); + } else { + return TTR("Folder name cannot be empty."); + } } - if (part.ends_with(" ") || part[0] == ' ') { - return TTR("Folder name cannot begin or end with a space."); + if (part.contains("\\") || part.contains(":") || part.contains("*") || + part.contains("|") || part.contains(">") || part.ends_with(".") || part.ends_with(" ")) { + if (is_file) { + return TTR("File name contains invalid characters."); + } else { + return TTR("Folder name contains invalid characters."); + } } if (part[0] == '.') { - return TTR("Folder name cannot begin with a dot."); + if (is_file) { + return TTR("File name begins with a dot."); + } else { + return TTR("Folder name begins with a dot."); + } } } @@ -81,12 +97,18 @@ String DirectoryCreateDialog::_validate_path(const String &p_path) const { } void DirectoryCreateDialog::_on_dir_path_changed() { - const String path = sanitize_input(dir_path->get_text()); + const String path = _sanitize_input(dir_path->get_text()); const String error = _validate_path(path); if (error.is_empty()) { if (path.contains("/")) { - validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); + if (mode == MODE_DIRECTORY) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in folder names will create subfolders recursively."), EditorValidationPanel::MSG_OK); + } else { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Using slashes in path will create the file in subfolder, creating new subfolders if necessary."), EditorValidationPanel::MSG_OK); + } + } else if (mode == MODE_FILE) { + validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("File name is valid."), EditorValidationPanel::MSG_OK); } } else { validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, error, EditorValidationPanel::MSG_ERROR); @@ -94,26 +116,13 @@ void DirectoryCreateDialog::_on_dir_path_changed() { } void DirectoryCreateDialog::ok_pressed() { - const String path = sanitize_input(dir_path->get_text()); + 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.is_empty(), error); - Error err; - Ref 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(SNAME("dir_created"), base_dir.path_join(path)); - } else { - EditorNode::get_singleton()->show_warning(TTR("Could not create folder.")); - } + accept_callback.call(base_dir.path_join(path)); hide(); } @@ -122,28 +131,40 @@ void DirectoryCreateDialog::_post_popup() { dir_path->grab_focus(); } -void DirectoryCreateDialog::config(const String &p_base_dir) { +void DirectoryCreateDialog::config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name) { + set_title(p_title); base_dir = p_base_dir; - label->set_text(vformat(TTR("Create new folder in %s:"), base_dir)); - dir_path->set_text("new folder"); - dir_path->select_all(); + base_path_label->set_text(vformat(TTR("Base path: %s"), base_dir)); + accept_callback = p_accept_callback; + mode = p_mode; + + dir_path->set_text(p_default_name); validation_panel->update(); -} -void DirectoryCreateDialog::_bind_methods() { - ADD_SIGNAL(MethodInfo("dir_created", PropertyInfo(Variant::STRING, "path"))); + if (p_mode == MODE_FILE) { + int extension_pos = p_default_name.rfind("."); + if (extension_pos > -1) { + dir_path->select(0, extension_pos); + return; + } + } + dir_path->select_all(); } DirectoryCreateDialog::DirectoryCreateDialog() { - set_title(TTR("Create Folder")); set_min_size(Size2i(480, 0) * EDSCALE); VBoxContainer *vb = memnew(VBoxContainer); add_child(vb); - label = memnew(Label); - label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); - vb->add_child(label); + base_path_label = memnew(Label); + base_path_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_WORD_ELLIPSIS); + vb->add_child(base_path_label); + + Label *name_label = memnew(Label); + name_label->set_text(TTR("Name:")); + name_label->set_theme_type_variation("HeaderSmall"); + vb->add_child(name_label); dir_path = memnew(LineEdit); vb->add_child(dir_path); diff --git a/editor/directory_create_dialog.h b/editor/directory_create_dialog.h index 82e2e98ae535..2292a6e5c9e2 100644 --- a/editor/directory_create_dialog.h +++ b/editor/directory_create_dialog.h @@ -40,23 +40,31 @@ class LineEdit; class DirectoryCreateDialog : public ConfirmationDialog { GDCLASS(DirectoryCreateDialog, ConfirmationDialog); +public: + enum Mode { + MODE_FILE, + MODE_DIRECTORY, + }; + +private: String base_dir; + Callable accept_callback; + int mode = MODE_FILE; - Label *label = nullptr; + Label *base_path_label = nullptr; LineEdit *dir_path = nullptr; EditorValidationPanel *validation_panel = nullptr; + String _sanitize_input(const String &p_input) const; String _validate_path(const String &p_path) const; void _on_dir_path_changed(); protected: - static void _bind_methods(); - virtual void ok_pressed() override; virtual void _post_popup() override; public: - void config(const String &p_base_dir); + void config(const String &p_base_dir, const Callable &p_accept_callback, int p_mode, const String &p_title, const String &p_default_name = ""); DirectoryCreateDialog(); }; diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index cd76fb631371..160d8f281595 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/extension/gdextension_manager.h" +#include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/io/resource_saver.h" #include "core/object/worker_thread_pool.h" @@ -419,11 +420,13 @@ void EditorFileSystem::_scan_filesystem() { new_filesystem->parent = nullptr; ScannedDirectory *sd; + HashSet *processed_files = nullptr; // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem. if (first_scan) { sd = first_scan_root_dir; // Will be updated on scan. ResourceUID::get_singleton()->clear(); + processed_files = memnew(HashSet()); } else { Ref d = DirAccess::create(DirAccess::ACCESS_RESOURCES); sd = memnew(ScannedDirectory); @@ -431,14 +434,18 @@ void EditorFileSystem::_scan_filesystem() { nb_files_total = _scan_new_dir(sd, d); } - _process_file_system(sd, new_filesystem, sp); + _process_file_system(sd, new_filesystem, sp, processed_files); + if (first_scan) { + _process_removed_files(*processed_files); + } dep_update_list.clear(); file_cache.clear(); //clear caches, no longer needed if (first_scan) { memdelete(first_scan_root_dir); first_scan_root_dir = nullptr; + memdelete(processed_files); } else { //on the first scan this is done from the main thread after re-importing _save_filesystem_cache(); @@ -1000,7 +1007,9 @@ void EditorFileSystem::scan() { void EditorFileSystem::ScanProgress::increment() { current++; float ratio = current / MAX(hi, 1.0f); - progress->step(ratio * 1000.0f); + if (progress) { + progress->step(ratio * 1000.0f); + } EditorFileSystem::singleton->scan_total = ratio; } @@ -1072,7 +1081,7 @@ int EditorFileSystem::_scan_new_dir(ScannedDirectory *p_dir, Ref &da) return nb_files_total_scan; } -void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { +void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet *r_processed_files) { p_dir->modified_time = FileAccess::get_modified_time(p_scan_dir->full_path); for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { @@ -1080,7 +1089,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, sub_dir->parent = p_dir; sub_dir->name = scan_sub_dir->name; p_dir->subdirs.push_back(sub_dir); - _process_file_system(scan_sub_dir, sub_dir, p_progress); + _process_file_system(scan_sub_dir, sub_dir, p_progress, r_processed_files); } for (const String &scan_file : p_scan_dir->files) { @@ -1096,6 +1105,10 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, fi->file = scan_file; p_dir->files.push_back(fi); + if (r_processed_files) { + r_processed_files->insert(path); + } + FileCache *fc = file_cache.getptr(path); uint64_t mt = FileAccess::get_modified_time(path); @@ -1240,7 +1253,21 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, } } -void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { +void EditorFileSystem::_process_removed_files(const HashSet &p_processed_files) { + for (const KeyValue &kv : file_cache) { + if (!p_processed_files.has(kv.key)) { + if (ClassDB::is_parent_class(kv.value.type, SNAME("Script"))) { + // A script has been removed from disk since the last startup. The documentation needs to be updated. + // There's no need to add the path in update_script_paths since that is exclusively for updating global class names, + // which is handled in _first_scan_filesystem before the full scan to ensure plugins and autoloads can be created. + MutexLock update_script_lock(update_script_mutex); + update_script_paths_documentation.insert(kv.key); + } + } + } +} + +void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, bool p_recursive) { uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path()); bool updated_dir = false; @@ -1307,7 +1334,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr int nb_files_dir = _scan_new_dir(&sd, d); p_progress.hi += nb_files_dir; diff_nb_files += nb_files_dir; - _process_file_system(&sd, efd, p_progress); + _process_file_system(&sd, efd, p_progress, nullptr); ItemAction ia; ia.action = ItemAction::ACTION_DIR_ADD; @@ -1435,7 +1462,9 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr scan_actions.push_back(ia); continue; } - _scan_fs_changes(p_dir->get_subdir(i), p_progress); + if (p_recursive) { + _scan_fs_changes(p_dir->get_subdir(i), p_progress); + } } nb_files_total = MAX(nb_files_total + diff_nb_files, 0); @@ -1935,6 +1964,13 @@ void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInf } } + if (icon_path.is_empty() && !file_info->type.is_empty()) { + Ref icon = EditorNode::get_singleton()->get_class_icon(file_info->type); + if (icon.is_valid()) { + icon_path = icon->get_path(); + } + } + file_info->icon_path = icon_path; } @@ -1960,25 +1996,31 @@ void EditorFileSystem::_update_script_classes() { return; } - update_script_mutex.lock(); + { + MutexLock update_script_lock(update_script_mutex); - EditorProgress *ep = nullptr; - if (update_script_paths.size() > 1) { - ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); - } + EditorProgress *ep = nullptr; + if (update_script_paths.size() > 1) { + if (MessageQueue::get_singleton()->is_flushing()) { + // Use background progress when message queue is flushing. + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size(), false, true)); + } else { + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); + } + } - int step_count = 0; - for (const KeyValue &E : update_script_paths) { - _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); - if (ep) { - ep->step(E.value.script_class_name, step_count++, false); + int step_count = 0; + for (const KeyValue &E : update_script_paths) { + _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); + if (ep) { + ep->step(E.value.script_class_name, step_count++, false); + } } - } - memdelete_notnull(ep); + memdelete_notnull(ep); - update_script_paths.clear(); - update_script_mutex.unlock(); + update_script_paths.clear(); + } ScriptServer::save_global_classes(); EditorNode::get_editor_data().script_class_save_icon_paths(); @@ -1999,11 +2041,16 @@ void EditorFileSystem::_update_script_documentation() { return; } - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); EditorProgress *ep = nullptr; if (update_script_paths_documentation.size() > 1) { - ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size())); + if (MessageQueue::get_singleton()->is_flushing()) { + // Use background progress when message queue is flushing. + ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size(), false, true)); + } else { + ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size())); + } } int step_count = 0; @@ -2038,7 +2085,6 @@ void EditorFileSystem::_update_script_documentation() { memdelete_notnull(ep); update_script_paths_documentation.clear(); - update_script_mutex.unlock(); } void EditorFileSystem::_process_update_pending() { @@ -2050,7 +2096,7 @@ void EditorFileSystem::_process_update_pending() { } void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) { - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); ScriptInfo si; si.type = p_type; @@ -2060,8 +2106,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St update_script_paths.insert(p_path, si); update_script_paths_documentation.insert(p_path); - - update_script_mutex.unlock(); } void EditorFileSystem::_update_scene_groups() { @@ -2075,31 +2119,32 @@ void EditorFileSystem::_update_scene_groups() { } int step_count = 0; - update_scene_mutex.lock(); - for (const String &path : update_scene_paths) { - ProjectSettings::get_singleton()->remove_scene_groups_cache(path); + { + MutexLock update_scene_lock(update_scene_mutex); + for (const String &path : update_scene_paths) { + ProjectSettings::get_singleton()->remove_scene_groups_cache(path); - int index = -1; - EditorFileSystemDirectory *efd = find_file(path, &index); + int index = -1; + EditorFileSystemDirectory *efd = find_file(path, &index); - if (!efd || index < 0) { - // The file was removed. - continue; - } + if (!efd || index < 0) { + // The file was removed. + continue; + } - const HashSet scene_groups = PackedScene::get_scene_groups(path); - if (!scene_groups.is_empty()) { - ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); - } + const HashSet scene_groups = PackedScene::get_scene_groups(path); + if (!scene_groups.is_empty()) { + ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); + } - if (ep) { - ep->step(efd->files[index]->file, step_count++, false); + if (ep) { + ep->step(efd->files[index]->file, step_count++, false); + } } - } - memdelete_notnull(ep); - update_scene_paths.clear(); - update_scene_mutex.unlock(); + memdelete_notnull(ep); + update_scene_paths.clear(); + } ProjectSettings::get_singleton()->save_scene_groups_cache(); } @@ -2114,9 +2159,8 @@ void EditorFileSystem::_update_pending_scene_groups() { } void EditorFileSystem::_queue_update_scene_groups(const String &p_path) { - update_scene_mutex.lock(); + MutexLock update_scene_lock(update_scene_mutex); update_scene_paths.insert(p_path); - update_scene_mutex.unlock(); } void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet &r_list) { @@ -2248,7 +2292,7 @@ void EditorFileSystem::update_files(const Vector &p_script_paths) { _queue_update_scene_groups(file); } - if (fs->files[cpos]->type == SNAME("Resource")) { + if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Resource"))) { files_to_update_icon_path.push_back(fs->files[cpos]); } else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) { update_files_icon_cache = true; @@ -2825,6 +2869,96 @@ void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file emit_signal(SNAME("resources_reimported"), reloads); } +Error EditorFileSystem::_copy_file(const String &p_from, const String &p_to) { + Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (FileAccess::exists(p_from + ".import")) { + Error err = da->copy(p_from, p_to); + if (err != OK) { + return err; + } + + // Remove uid from .import file to avoid conflict. + Ref cfg; + cfg.instantiate(); + cfg->load(p_from + ".import"); + cfg->erase_section_key("remap", "uid"); + err = cfg->save(p_to + ".import"); + if (err != OK) { + return err; + } + } else if (ResourceLoader::get_resource_uid(p_from) == ResourceUID::INVALID_ID) { + // Files which do not use an uid can just be copied. + Error err = da->copy(p_from, p_to); + if (err != OK) { + return err; + } + } else { + // Load the resource and save it again in the new location (this generates a new UID). + Error err; + Ref res = ResourceLoader::load(p_from, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); + if (err == OK && res.is_valid()) { + err = ResourceSaver::save(res, p_to, ResourceSaver::FLAG_COMPRESS); + if (err != OK) { + return err; + } + } else if (err != OK) { + // When loading files like text files the error is OK but the resource is still null. + // We can ignore such files. + return err; + } + } + return OK; +} + +bool EditorFileSystem::_copy_directory(const String &p_from, const String &p_to, List *p_files) { + Ref old_dir = DirAccess::open(p_from); + ERR_FAIL_COND_V(old_dir.is_null(), false); + + Error err = make_dir_recursive(p_to); + if (err != OK && err != ERR_ALREADY_EXISTS) { + return false; + } + + bool success = true; + old_dir->set_include_navigational(false); + old_dir->list_dir_begin(); + + for (String F = old_dir->_get_next(); !F.is_empty(); F = old_dir->_get_next()) { + if (old_dir->current_is_dir()) { + success = _copy_directory(p_from.path_join(F), p_to.path_join(F), p_files) && success; + } else if (F.get_extension() != "import") { + CopiedFile copy; + copy.from = p_from.path_join(F); + copy.to = p_to.path_join(F); + p_files->push_back(copy); + } + } + return success; +} + +void EditorFileSystem::_queue_refresh_filesystem() { + if (refresh_queued) { + return; + } + refresh_queued = true; + get_tree()->connect(SNAME("process_frame"), callable_mp(this, &EditorFileSystem::_refresh_filesystem), CONNECT_ONE_SHOT); +} + +void EditorFileSystem::_refresh_filesystem() { + for (const ObjectID &id : folders_to_sort) { + EditorFileSystemDirectory *dir = Object::cast_to(ObjectDB::get_instance(id)); + if (dir) { + dir->subdirs.sort_custom(); + } + } + folders_to_sort.clear(); + + _update_scan_actions(); + + emit_signal(SNAME("filesystem_changed")); + refresh_queued = false; +} + void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) { int current_max = p_import_data->reimport_from + int(p_index); p_import_data->max_index.exchange_if_greater(current_max); @@ -3128,33 +3262,91 @@ void EditorFileSystem::move_group_file(const String &p_path, const String &p_new } } -void EditorFileSystem::add_new_directory(const String &p_path) { - String path = p_path.get_base_dir(); - EditorFileSystemDirectory *parent = filesystem; - int base = p_path.count("/"); - int max_bit = base + 1; +Error EditorFileSystem::make_dir_recursive(const String &p_path, const String &p_base_path) { + Error err; + Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + if (!p_base_path.is_empty()) { + err = da->change_dir(p_base_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot open base directory '" + p_base_path + "'."); + } + + if (da->dir_exists(p_path)) { + return ERR_ALREADY_EXISTS; + } - while (path != "res://") { - EditorFileSystemDirectory *dir = get_filesystem_path(path); - if (dir) { - parent = dir; - break; - } - path = path.get_base_dir(); - base--; + err = da->make_dir_recursive(p_path); + if (err != OK) { + return err; } - for (int i = base; i < max_bit; i++) { + const String path = da->get_current_dir(); + EditorFileSystemDirectory *parent = get_filesystem_path(path); + ERR_FAIL_NULL_V(parent, ERR_FILE_NOT_FOUND); + folders_to_sort.insert(parent->get_instance_id()); + + const PackedStringArray folders = p_path.trim_prefix(path).trim_suffix("/").split("/"); + for (const String &folder : folders) { + const int current = parent->find_dir_index(folder); + if (current > -1) { + parent = parent->get_subdir(current); + continue; + } + EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory); efd->parent = parent; - efd->name = p_path.get_slice("/", i); + efd->name = folder; parent->subdirs.push_back(efd); + parent = efd; + } + + _queue_refresh_filesystem(); + return OK; +} + +Error EditorFileSystem::copy_file(const String &p_from, const String &p_to) { + _copy_file(p_from, p_to); + + EditorFileSystemDirectory *parent = get_filesystem_path(p_to.get_base_dir()); + ERR_FAIL_NULL_V(parent, ERR_FILE_NOT_FOUND); + + ScanProgress sp; + _scan_fs_changes(parent, sp, false); + + _queue_refresh_filesystem(); + return OK; +} + +Error EditorFileSystem::copy_directory(const String &p_from, const String &p_to) { + List files; + bool success = _copy_directory(p_from, p_to, &files); - if (i == base) { - parent->subdirs.sort_custom(); + EditorProgress *ep = nullptr; + if (files.size() > 10) { + ep = memnew(EditorProgress("_copy_files", TTR("Copying files..."), files.size())); + } + + int i = 0; + for (const CopiedFile &F : files) { + if (_copy_file(F.from, F.to) != OK) { + success = false; + } + if (ep) { + ep->step(F.from.get_file(), i++, false); } - parent = efd; } + memdelete_notnull(ep); + + EditorFileSystemDirectory *efd = get_filesystem_path(p_to); + ERR_FAIL_NULL_V(efd, FAILED); + ERR_FAIL_NULL_V(efd->get_parent(), FAILED); + + folders_to_sort.insert(efd->get_parent()->get_instance_id()); + + ScanProgress sp; + _scan_fs_changes(efd, sp); + + _queue_refresh_filesystem(); + return success ? OK : FAILED; } ResourceUID::ID EditorFileSystem::_resource_saver_get_resource_id_for_path(const String &p_path, bool p_generate) { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 3da407a813e3..9d7c49247dfe 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -241,7 +241,7 @@ class EditorFileSystem : public Node { bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const; - void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress); + void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, bool p_recursive = true); void _delete_internal_files(const String &p_file); int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir); @@ -252,7 +252,7 @@ class EditorFileSystem : public Node { HashSet import_extensions; int _scan_new_dir(ScannedDirectory *p_dir, Ref &da); - void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress); + void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet *p_processed_files); Thread thread_sources; bool scanning_changes = false; @@ -302,6 +302,7 @@ class EditorFileSystem : public Node { void _update_script_classes(); void _update_script_documentation(); void _process_update_pending(); + void _process_removed_files(const HashSet &p_processed_files); Mutex update_scene_mutex; HashSet update_scene_paths; @@ -324,6 +325,19 @@ class EditorFileSystem : public Node { HashSet group_file_cache; HashMap file_icon_cache; + struct CopiedFile { + String from; + String to; + }; + + bool refresh_queued = false; + HashSet folders_to_sort; + + Error _copy_file(const String &p_from, const String &p_to); + bool _copy_directory(const String &p_from, const String &p_to, List *p_files); + void _queue_refresh_filesystem(); + void _refresh_filesystem(); + struct ImportThreadData { const ImportFile *reimport_files; int reimport_from; @@ -377,7 +391,9 @@ class EditorFileSystem : public Node { bool is_group_file(const String &p_path) const; void move_group_file(const String &p_path, const String &p_new_path); - void add_new_directory(const String &p_path); + Error make_dir_recursive(const String &p_path, const String &p_base_path = String()); + Error copy_file(const String &p_from, const String &p_to); + Error copy_directory(const String &p_from, const String &p_to); static bool _should_skip_directory(const String &p_path); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index db9555e24a53..35178fe52baa 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -184,7 +184,7 @@ static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build t static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."; bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) { - if (Thread::is_main_thread()) { + if (!force_background && Thread::is_main_thread()) { return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); } else { EditorNode::progress_task_step_bg(task, p_step); @@ -192,17 +192,18 @@ bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refres } } -EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) { - if (Thread::is_main_thread()) { +EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel, bool p_force_background) { + if (!p_force_background && Thread::is_main_thread()) { EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel); } else { EditorNode::progress_add_task_bg(p_task, p_label, p_amount); } task = p_task; + force_background = p_force_background; } EditorProgress::~EditorProgress() { - if (Thread::is_main_thread()) { + if (!force_background && Thread::is_main_thread()) { EditorNode::progress_end_task(task); } else { EditorNode::progress_end_task_bg(task); diff --git a/editor/editor_node.h b/editor/editor_node.h index 7cb55fc43cbf..20366e2023bb 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -124,9 +124,10 @@ class WindowWrapper; struct EditorProgress { String task; + bool force_background = false; bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true); - EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false); + EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false, bool p_force_background = false); ~EditorProgress(); }; diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index fd675d33a614..0eced2bf74da 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -218,6 +218,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory // Set custom folder color (if applicable). bool has_custom_color = assigned_folder_colors.has(lpath); Color custom_color = has_custom_color ? folder_colors[assigned_folder_colors[lpath]] : Color(); + Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (has_custom_color) { subdirectory_item->set_icon_modulate(0, editor_is_dark_theme ? custom_color : custom_color * ITEM_COLOR_SCALE); @@ -239,6 +240,10 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory subdirectory_item->set_text(0, dname); subdirectory_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE); subdirectory_item->set_icon(0, get_editor_theme_icon(SNAME("Folder"))); + if (da->is_link(lpath)) { + subdirectory_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay"))); + subdirectory_item->set_tooltip_text(0, vformat(TTR("Link to: %s"), da->read_link(lpath))); + } subdirectory_item->set_selectable(0, true); subdirectory_item->set_metadata(0, lpath); if (!p_select_in_favorites && (current_path == lpath || ((display_mode != DISPLAY_MODE_TREE_ONLY) && current_path.get_base_dir() == lpath))) { @@ -311,6 +316,10 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_text(0, fi.name); file_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE); file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type, fi.icon_path)); + if (da->is_link(file_metadata)) { + file_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay"))); + file_item->set_tooltip_text(0, vformat(TTR("Link to: %s"), da->read_link(file_metadata))); + } file_item->set_icon_max_width(0, icon_size); Color parent_bg_color = subdirectory_item->get_custom_bg_color(0); if (has_custom_color) { @@ -640,8 +649,7 @@ void FileSystemDock::_notification(int p_what) { } if (do_redraw) { - _update_file_list(true); - _update_tree(get_uncollapsed_paths()); + update_all(); } if (EditorThemeManager::is_generated_theme_outdated()) { @@ -1293,13 +1301,7 @@ void FileSystemDock::_fs_changed() { scanning_vb->hide(); split_box->show(); - if (tree->is_visible()) { - _update_tree(get_uncollapsed_paths()); - } - - if (file_list_vb->is_visible()) { - _update_file_list(true); - } + update_all(); if (!select_after_scan.is_empty()) { _navigate_to_path(select_after_scan); @@ -1311,15 +1313,6 @@ void FileSystemDock::_fs_changed() { set_process(false); } -void FileSystemDock::_directory_created(const String &p_path) { - if (!DirAccess::exists(p_path)) { - return; - } - EditorFileSystem::get_singleton()->add_new_directory(p_path); - _update_tree(get_uncollapsed_paths()); - _update_file_list(true); -} - void FileSystemDock::_set_scanning_mode() { button_hist_prev->set_disabled(true); button_hist_next->set_disabled(true); @@ -1496,76 +1489,22 @@ void FileSystemDock::_try_duplicate_item(const FileOrFolder &p_item, const Strin EditorNode::get_singleton()->add_io_error(TTR("Cannot move a folder into itself.") + "\n" + old_path + "\n"); return; } - Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (p_item.is_file) { print_verbose("Duplicating " + old_path + " -> " + new_path); // Create the directory structure. - da->make_dir_recursive(new_path.get_base_dir()); - - if (FileAccess::exists(old_path + ".import")) { - Error err = da->copy(old_path, new_path); - if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n"); - return; - } + EditorFileSystem::get_singleton()->make_dir_recursive(p_new_path.get_base_dir()); - // Remove uid from .import file to avoid conflict. - Ref cfg; - cfg.instantiate(); - cfg->load(old_path + ".import"); - cfg->erase_section_key("remap", "uid"); - err = cfg->save(new_path + ".import"); - if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ".import: " + error_names[err] + "\n"); - return; - } - } else { - // Files which do not use an uid can just be copied. - if (ResourceLoader::get_resource_uid(old_path) == ResourceUID::INVALID_ID) { - Error err = da->copy(old_path, new_path); - if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n"); - } - return; - } - - // Load the resource and save it again in the new location (this generates a new UID). - Error err; - Ref res = ResourceLoader::load(old_path, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); - if (err == OK && res.is_valid()) { - err = ResourceSaver::save(res, new_path, ResourceSaver::FLAG_COMPRESS); - if (err != OK) { - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to save resource at %s: %s"), new_path, error_names[err])); - } - } else if (err != OK) { - // When loading files like text files the error is OK but the resource is still null. - // We can ignore such files. - EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + " " + vformat(TTR("Failed to load resource at %s: %s"), new_path, error_names[err])); - } + Error err = EditorFileSystem::get_singleton()->copy_file(old_path, new_path); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating:") + "\n" + old_path + ": " + error_names[err] + "\n"); } } else { - da->make_dir(new_path); - - // Recursively duplicate all files inside the folder. - Ref old_dir = DirAccess::open(old_path); - ERR_FAIL_COND(old_dir.is_null()); - - Ref file_access = FileAccess::create(FileAccess::ACCESS_RESOURCES); - old_dir->set_include_navigational(false); - old_dir->list_dir_begin(); - for (String f = old_dir->_get_next(); !f.is_empty(); f = old_dir->_get_next()) { - if (f.get_extension() == "import") { - continue; - } - if (file_access->file_exists(old_path + f)) { - _try_duplicate_item(FileOrFolder(old_path + f, true), new_path + f); - } else if (da->dir_exists(old_path + f)) { - _try_duplicate_item(FileOrFolder(old_path + f, false), new_path + f); - } + Error err = EditorFileSystem::get_singleton()->copy_directory(old_path, new_path); + if (err != OK) { + EditorNode::get_singleton()->add_io_error(TTR("Error duplicating directory:") + "\n" + old_path + "\n"); } - old_dir->list_dir_end(); } } @@ -1870,39 +1809,16 @@ void FileSystemDock::_rename_operation_confirm() { _rescan(); } -void FileSystemDock::_duplicate_operation_confirm() { - String new_name = duplicate_dialog_text->get_text().strip_edges(); - if (new_name.length() == 0) { - EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; - } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { - EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); - return; - } else if (new_name[0] == '.') { - EditorNode::get_singleton()->show_warning(TTR("Name begins with a dot.")); - return; - } - - String base_dir = to_duplicate.path.get_base_dir(); - // get_base_dir() returns "some/path" if the original path was "some/path/", so work it around. - if (to_duplicate.path.ends_with("/")) { - base_dir = base_dir.get_base_dir(); - } - - String new_path = base_dir.path_join(new_name); - - // Present a more user friendly warning for name conflict - Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - if (da->file_exists(new_path) || da->dir_exists(new_path)) { - EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); - return; +void FileSystemDock::_duplicate_operation_confirm(const String &p_path) { + const String base_dir = p_path.trim_suffix("/").get_base_dir(); + if (!DirAccess::dir_exists_absolute(base_dir)) { + Error err = EditorFileSystem::get_singleton()->make_dir_recursive(base_dir); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create base directory: %s"), error_names[err])); + return; + } } - - _try_duplicate_item(to_duplicate, new_path); - - // Rescan everything. - print_verbose("FileSystem: calling rescan."); - _rescan(); + _try_duplicate_item(to_duplicate, p_path); } void FileSystemDock::_overwrite_dialog_action(bool p_overwrite) { @@ -2478,24 +2394,22 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected } break; case FILE_DUPLICATE: { - // Duplicate the selected files. - for (int i = 0; i < p_selected.size(); i++) { - to_duplicate.path = p_selected[i]; - to_duplicate.is_file = !to_duplicate.path.ends_with("/"); - if (to_duplicate.is_file) { - String name = to_duplicate.path.get_file(); - duplicate_dialog->set_title(TTR("Duplicating file:") + " " + name); - duplicate_dialog_text->set_text(name); - duplicate_dialog_text->select(0, name.rfind(".")); - } else { - String name = to_duplicate.path.substr(0, to_duplicate.path.length() - 1).get_file(); - duplicate_dialog->set_title(TTR("Duplicating folder:") + " " + name); - duplicate_dialog_text->set_text(name); - duplicate_dialog_text->select(0, name.length()); - } - duplicate_dialog->popup_centered(Size2(250, 80) * EDSCALE); - duplicate_dialog_text->grab_focus(); + if (p_selected.size() != 1) { + return; } + + to_duplicate.path = p_selected[0]; + to_duplicate.is_file = !to_duplicate.path.ends_with("/"); + if (to_duplicate.is_file) { + String name = to_duplicate.path.get_file(); + make_dir_dialog->config(to_duplicate.path.get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm), + DirectoryCreateDialog::MODE_FILE, TTR("Duplicating file:") + " " + name, name); + } else { + String name = to_duplicate.path.trim_suffix("/").get_file(); + make_dir_dialog->config(to_duplicate.path.trim_suffix("/").get_base_dir(), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm), + DirectoryCreateDialog::MODE_DIRECTORY, TTR("Duplicating folder:") + " " + name, name); + } + make_dir_dialog->popup_centered(); } break; case FILE_INFO: { @@ -2510,7 +2424,8 @@ void FileSystemDock::_file_option(int p_option, const Vector &p_selected if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } - make_dir_dialog->config(directory); + make_dir_dialog->config(directory, callable_mp(this, &FileSystemDock::create_directory).bind(directory), + DirectoryCreateDialog::MODE_DIRECTORY, TTR("Create Folder"), "new folder"); make_dir_dialog->popup_centered(); } break; @@ -2684,6 +2599,16 @@ void FileSystemDock::fix_dependencies(const String &p_for_file) { deps_editor->edit(p_for_file); } +void FileSystemDock::update_all() { + if (tree->is_visible()) { + _update_tree(get_uncollapsed_paths()); + } + + if (file_list_vb->is_visible()) { + _update_file_list(true); + } +} + void FileSystemDock::focus_on_path() { current_path_line_edit->grab_focus(); current_path_line_edit->select_all(); @@ -2703,6 +2628,13 @@ void FileSystemDock::focus_on_filter() { } } +void FileSystemDock::create_directory(const String &p_path, const String &p_base_dir) { + Error err = EditorFileSystem::get_singleton()->make_dir_recursive(p_path, p_base_dir); + if (err != OK) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Could not create folder: %s"), error_names[err])); + } +} + ScriptCreateDialog *FileSystemDock::get_script_create_dialog() const { return make_script_dialog; } @@ -3105,9 +3037,7 @@ void FileSystemDock::_folder_color_index_pressed(int p_index, PopupMenu *p_menu) } _update_folder_colors_setting(); - - _update_tree(get_uncollapsed_paths()); - _update_file_list(true); + update_all(); emit_signal(SNAME("folder_color_changed")); } @@ -3759,8 +3689,7 @@ void FileSystemDock::set_file_sort(FileSortOption p_file_sort) { file_sort = p_file_sort; // Update everything needed. - _update_tree(get_uncollapsed_paths()); - _update_file_list(true); + update_all(); } void FileSystemDock::_file_sort_popup(int p_id) { @@ -3960,22 +3889,25 @@ FileSystemDock::FileSystemDock() { add_child(top_vbc); HBoxContainer *toolbar_hbc = memnew(HBoxContainer); - toolbar_hbc->add_theme_constant_override("separation", 0); top_vbc->add_child(toolbar_hbc); + HBoxContainer *nav_hbc = memnew(HBoxContainer); + nav_hbc->add_theme_constant_override("separation", 0); + toolbar_hbc->add_child(nav_hbc); + button_hist_prev = memnew(Button); button_hist_prev->set_flat(true); button_hist_prev->set_disabled(true); button_hist_prev->set_focus_mode(FOCUS_NONE); button_hist_prev->set_tooltip_text(TTR("Go to previous selected folder/file.")); - toolbar_hbc->add_child(button_hist_prev); + nav_hbc->add_child(button_hist_prev); button_hist_next = memnew(Button); button_hist_next->set_flat(true); button_hist_next->set_disabled(true); button_hist_next->set_focus_mode(FOCUS_NONE); button_hist_next->set_tooltip_text(TTR("Go to next selected folder/file.")); - toolbar_hbc->add_child(button_hist_next); + nav_hbc->add_child(button_hist_next); current_path_line_edit = memnew(LineEdit); current_path_line_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); @@ -3998,13 +3930,12 @@ FileSystemDock::FileSystemDock() { toolbar_hbc->add_child(button_toggle_display_mode); button_dock_placement = memnew(Button); - button_dock_placement->set_flat(true); + button_dock_placement->set_theme_type_variation("FlatMenuButton"); button_dock_placement->connect(SceneStringName(pressed), callable_mp(this, &FileSystemDock::_change_bottom_dock_placement)); button_dock_placement->hide(); toolbar_hbc->add_child(button_dock_placement); toolbar2_hbc = memnew(HBoxContainer); - toolbar2_hbc->add_theme_constant_override("separation", 0); top_vbc->add_child(toolbar2_hbc); tree_search_box = memnew(LineEdit); @@ -4070,7 +4001,7 @@ FileSystemDock::FileSystemDock() { path_hb->add_child(file_list_button_sort); button_file_list_display_mode = memnew(Button); - button_file_list_display_mode->set_flat(true); + button_file_list_display_mode->set_theme_type_variation("FlatMenuButton"); path_hb->add_child(button_file_list_display_mode); files = memnew(FileSystemList); @@ -4137,20 +4068,8 @@ FileSystemDock::FileSystemDock() { overwrite_dialog_footer = memnew(Label); overwrite_dialog_vb->add_child(overwrite_dialog_footer); - duplicate_dialog = memnew(ConfirmationDialog); - VBoxContainer *duplicate_dialog_vb = memnew(VBoxContainer); - duplicate_dialog->add_child(duplicate_dialog_vb); - - duplicate_dialog_text = memnew(LineEdit); - duplicate_dialog_vb->add_margin_child(TTR("Name:"), duplicate_dialog_text); - duplicate_dialog->set_ok_button_text(TTR("Duplicate")); - add_child(duplicate_dialog); - duplicate_dialog->register_text_enter(duplicate_dialog_text); - duplicate_dialog->connect(SceneStringName(confirmed), callable_mp(this, &FileSystemDock::_duplicate_operation_confirm)); - make_dir_dialog = memnew(DirectoryCreateDialog); add_child(make_dir_dialog); - make_dir_dialog->connect("dir_created", callable_mp(this, &FileSystemDock::_directory_created)); make_scene_dialog = memnew(SceneCreateDialog); add_child(make_scene_dialog); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index b61cc4141d07..1242aab2c7c1 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -194,8 +194,6 @@ class FileSystemDock : public VBoxContainer { DependencyRemoveDialog *remove_dialog = nullptr; EditorDirDialog *move_dialog = nullptr; - ConfirmationDialog *duplicate_dialog = nullptr; - LineEdit *duplicate_dialog_text = nullptr; DirectoryCreateDialog *make_dir_dialog = nullptr; ConfirmationDialog *overwrite_dialog = nullptr; @@ -263,7 +261,6 @@ class FileSystemDock : public VBoxContainer { void _toggle_file_display(); void _set_file_display(bool p_active); void _fs_changed(); - void _directory_created(const String &p_path); void _select_file(const String &p_path, bool p_select_in_favorites = false); void _tree_activate_file(); @@ -294,7 +291,7 @@ class FileSystemDock : public VBoxContainer { void _resource_created(); void _make_scene_confirm(); void _rename_operation_confirm(); - void _duplicate_operation_confirm(); + void _duplicate_operation_confirm(const String &p_path); void _overwrite_dialog_action(bool p_overwrite); Vector _check_existing(); void _move_operation_confirm(const String &p_to_path, bool p_copy = false, Overwrite p_overwrite = OVERWRITE_UNDECIDED); @@ -404,10 +401,12 @@ class FileSystemDock : public VBoxContainer { void navigate_to_path(const String &p_path); void focus_on_path(); void focus_on_filter(); + void create_directory(const String &p_path, const String &p_base_dir); ScriptCreateDialog *get_script_create_dialog() const; void fix_dependencies(const String &p_for_file); + void update_all(); int get_h_split_offset() const { return split_box_offset_h; } void set_h_split_offset(int p_offset) { split_box_offset_h = p_offset; } diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp index 50ff6c3d71e2..4edb88b82e7f 100644 --- a/editor/gui/editor_dir_dialog.cpp +++ b/editor/gui/editor_dir_dialog.cpp @@ -177,11 +177,14 @@ void EditorDirDialog::ok_pressed() { void EditorDirDialog::_make_dir() { TreeItem *ti = tree->get_selected(); ERR_FAIL_NULL(ti); - makedialog->config(ti->get_metadata(0)); + const String &directory = ti->get_metadata(0); + makedialog->config(directory, callable_mp(this, &EditorDirDialog::_make_dir_confirm).bind(directory), DirectoryCreateDialog::MODE_DIRECTORY, "new folder"); makedialog->popup_centered(); } -void EditorDirDialog::_make_dir_confirm(const String &p_path) { +void EditorDirDialog::_make_dir_confirm(const String &p_path, const String &p_base_dir) { + FileSystemDock::get_singleton()->create_directory(p_path, p_base_dir); + // Multiple level of directories can be created at once. String base_dir = p_path.get_base_dir(); while (true) { @@ -193,7 +196,6 @@ void EditorDirDialog::_make_dir_confirm(const String &p_path) { } new_dir_path = p_path + "/"; - EditorFileSystem::get_singleton()->scan_changes(); // We created a dir, so rescan changes. } void EditorDirDialog::_bind_methods() { @@ -231,5 +233,4 @@ EditorDirDialog::EditorDirDialog() { makedialog = memnew(DirectoryCreateDialog); add_child(makedialog); - makedialog->connect("dir_created", callable_mp(this, &EditorDirDialog::_make_dir_confirm)); } diff --git a/editor/gui/editor_dir_dialog.h b/editor/gui/editor_dir_dialog.h index b10cc8dd9f5e..491eb6174936 100644 --- a/editor/gui/editor_dir_dialog.h +++ b/editor/gui/editor_dir_dialog.h @@ -56,7 +56,7 @@ class EditorDirDialog : public ConfirmationDialog { void _update_dir(const Color &p_default_folder_color, const Dictionary &p_assigned_folder_colors, const HashMap &p_folder_colors, bool p_is_dark_theme, TreeItem *p_item, EditorFileSystemDirectory *p_dir, const String &p_select_path = String()); void _make_dir(); - void _make_dir_confirm(const String &p_path); + void _make_dir_confirm(const String &p_path, const String &p_base_dir); void _copy_pressed(); void ok_pressed() override; diff --git a/editor/icons/LinkOverlay.svg b/editor/icons/LinkOverlay.svg new file mode 100644 index 000000000000..b263f94c0b68 --- /dev/null +++ b/editor/icons/LinkOverlay.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 8c2fb4246350..3847364a2b4a 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector finished_task_ids; + LocalVector finished_task_ids; - for (KeyValue &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser2D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::finish() { @@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Refnavigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Refstatus = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator2D::is_baking(Ref p_navigation_polygon) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_polygon); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_polygon); } void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index d17724baa047..e92a9d304b0a 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector finished_task_ids; + LocalVector finished_task_ids; - for (KeyValue &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask3D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask3D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser3D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::finish() { @@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Refnavigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Refstatus = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D")); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator3D::is_baking(Ref p_navigation_mesh) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_mesh); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_mesh); } void NavMeshGenerator3D::generator_thread_bake(void *p_arg) { diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index eb0b7c58b1a8..05897f7af011 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -66,8 +66,15 @@ void TreeItem::Cell::draw_icon(const RID &p_where, const Point2 &p_pos, const Si if (icon_region == Rect2i()) { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), Rect2(Point2(), icon->get_size()), p_color); + if (icon_overlay.is_valid()) { + Vector2 offset = icon->get_size() - icon_overlay->get_size(); + icon_overlay->draw_rect_region(p_where, Rect2(p_pos + offset, dsize), Rect2(Point2(), icon_overlay->get_size()), p_color); + } } else { icon->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + if (icon_overlay.is_valid()) { + icon_overlay->draw_rect_region(p_where, Rect2(p_pos, dsize), icon_region, p_color); + } } } @@ -481,6 +488,24 @@ Ref TreeItem::get_icon(int p_column) const { return cells[p_column].icon; } +void TreeItem::set_icon_overlay(int p_column, const Ref &p_icon_overlay) { + ERR_FAIL_INDEX(p_column, cells.size()); + + if (cells[p_column].icon_overlay == p_icon_overlay) { + return; + } + + cells.write[p_column].icon_overlay = p_icon_overlay; + cells.write[p_column].cached_minimum_size_dirty = true; + + _changed_notify(p_column); +} + +Ref TreeItem::get_icon_overlay(int p_column) const { + ERR_FAIL_INDEX_V(p_column, cells.size(), Ref()); + return cells[p_column].icon_overlay; +} + void TreeItem::set_icon_region(int p_column, const Rect2 &p_icon_region) { ERR_FAIL_INDEX(p_column, cells.size()); @@ -1635,6 +1660,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("set_icon", "column", "texture"), &TreeItem::set_icon); ClassDB::bind_method(D_METHOD("get_icon", "column"), &TreeItem::get_icon); + ClassDB::bind_method(D_METHOD("set_icon_overlay", "column", "texture"), &TreeItem::set_icon_overlay); + ClassDB::bind_method(D_METHOD("get_icon_overlay", "column"), &TreeItem::get_icon_overlay); + ClassDB::bind_method(D_METHOD("set_icon_region", "column", "region"), &TreeItem::set_icon_region); ClassDB::bind_method(D_METHOD("get_icon_region", "column"), &TreeItem::get_icon_region); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index bc5e0e585dde..e021acb0642e 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -63,6 +63,7 @@ class TreeItem : public Object { TreeCellMode mode = TreeItem::CELL_MODE_STRING; Ref icon; + Ref icon_overlay; Rect2i icon_region; String text; String xl_text; @@ -256,6 +257,9 @@ class TreeItem : public Object { void set_icon(int p_column, const Ref &p_icon); Ref get_icon(int p_column) const; + void set_icon_overlay(int p_column, const Ref &p_icon_overlay); + Ref get_icon_overlay(int p_column) const; + void set_icon_region(int p_column, const Rect2 &p_icon_region); Rect2 get_icon_region(int p_column) const; diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp index 5e4f1970ee8e..92246b50db63 100644 --- a/scene/resources/3d/fog_material.cpp +++ b/scene/resources/3d/fog_material.cpp @@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() { } void FogMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); @@ -165,7 +165,6 @@ void fog() { } )"); } - shader_mutex.unlock(); } FogMaterial::FogMaterial() { diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp index 640261d615ef..c470db5d7f23 100644 --- a/scene/resources/3d/sky_material.cpp +++ b/scene/resources/3d/sky_material.cpp @@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() { } void ProceduralSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -354,7 +354,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - shader_mutex.unlock(); } ProceduralSkyMaterial::ProceduralSkyMaterial() { @@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() { } void PanoramaSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -484,8 +483,6 @@ void sky() { i ? "filter_linear" : "filter_nearest")); } } - - shader_mutex.unlock(); } PanoramaSkyMaterial::PanoramaSkyMaterial() { @@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() { } void PhysicalSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -785,8 +782,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - - shader_mutex.unlock(); } PhysicalSkyMaterial::PhysicalSkyMaterial() {