diff options
41 files changed, 1201 insertions, 766 deletions
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index 38f41d645c..b4da314e96 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -445,13 +445,12 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { WARN_PRINT("Broken external resource! (index out of size)"); r_v = Variant(); } else { - if (external_resources[erindex].cache.is_null()) { - //cache not here yet, wait for it? - if (use_sub_threads) { - Error err; - external_resources.write[erindex].cache = ResourceLoader::load_threaded_get(external_resources[erindex].path, &err); - - if (err != OK || external_resources[erindex].cache.is_null()) { + Ref<ResourceLoader::LoadToken> &load_token = external_resources.write[erindex].load_token; + if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies. + Error err; + Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err); + if (res.is_null()) { + if (!ResourceLoader::is_cleaning_tasks()) { if (!ResourceLoader::get_abort_on_missing_resources()) { ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type); } else { @@ -459,12 +458,11 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) { ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + "."); } } + } else { + r_v = res; } } - - r_v = external_resources[erindex].cache; } - } break; default: { ERR_FAIL_V(ERR_FILE_CORRUPT); @@ -684,28 +682,13 @@ Error ResourceLoaderBinary::load() { } external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap - - if (!use_sub_threads) { - external_resources.write[i].cache = ResourceLoader::load(path, external_resources[i].type); - - if (external_resources[i].cache.is_null()) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } - } - - } else { - Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); - if (err != OK) { - if (!ResourceLoader::get_abort_on_missing_resources()) { - ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); - } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); - } + external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + if (!external_resources[i].load_token.is_valid()) { + if (!ResourceLoader::get_abort_on_missing_resources()) { + ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type); + } else { + error = ERR_FILE_MISSING_DEPENDENCIES; + ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + "."); } } } diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h index add7cdf297..30f1664983 100644 --- a/core/io/resource_format_binary.h +++ b/core/io/resource_format_binary.h @@ -60,7 +60,7 @@ class ResourceLoaderBinary { String path; String type; ResourceUID::ID uid = ResourceUID::INVALID_ID; - Ref<Resource> cache; + Ref<ResourceLoader::LoadToken> load_token; }; bool using_named_scene_ids = false; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 9af3a7daed..a27341dd2c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -202,20 +202,71 @@ void ResourceFormatLoader::_bind_methods() { /////////////////////////////////// +// This should be robust enough to be called redundantly without issues. +void ResourceLoader::LoadToken::clear() { + thread_load_mutex.lock(); + + WorkerThreadPool::TaskID task_to_await = 0; + + if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered. + DEV_ASSERT(thread_load_tasks.has(local_path)); + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + if (!load_task.awaited) { + task_to_await = load_task.task_id; + load_task.awaited = true; + } + thread_load_tasks.erase(local_path); + local_path.clear(); + } + + if (!user_path.is_empty()) { + DEV_ASSERT(user_load_tokens.has(user_path)); + user_load_tokens.erase(user_path); + user_path.clear(); + } + + thread_load_mutex.unlock(); + + // If task is unused, await it here, locally, now the token data is consistent. + if (task_to_await) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await); + } +} + +ResourceLoader::LoadToken::~LoadToken() { + clear(); +} + Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) { - bool found = false; + load_nesting++; + if (load_paths_stack.size()) { + thread_load_mutex.lock(); + HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack[load_paths_stack.size() - 1]); + if (E) { + E->value.sub_tasks.insert(p_path); + } + thread_load_mutex.unlock(); + } + load_paths_stack.push_back(p_path); // Try all loaders and pick the first match for the type hint + bool found = false; + Ref<Resource> res; for (int i = 0; i < loader_count; i++) { if (!loader[i]->recognize_path(p_path, p_type_hint)) { continue; } found = true; - Ref<Resource> res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); - if (res.is_null()) { - continue; + res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); + if (!res.is_null()) { + break; } + } + + load_paths_stack.resize(load_paths_stack.size() - 1); + load_nesting--; + if (!res.is_null()) { return res; } @@ -232,47 +283,60 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin void ResourceLoader::_thread_load_function(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - load_task.loader_id = Thread::get_caller_id(); - if (load_task.cond_var) { - //this is an actual thread, so wait for Ok from semaphore - thread_load_semaphore->wait(); //wait until its ok to start loading + thread_load_mutex.lock(); + caller_task_id = load_task.task_id; + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + thread_load_mutex.unlock(); + return; } - load_task.resource = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + thread_load_mutex.unlock(); - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + // Thread-safe either if it's the current thread or a brand new one. + CallQueue *mq_override = nullptr; + if (load_nesting == 0) { + if (!load_task.dependent_path.is_empty()) { + load_paths_stack.push_back(load_task.dependent_path); + } + if (!Thread::is_main_thread()) { + mq_override = memnew(CallQueue); + MessageQueue::set_thread_singleton_override(mq_override); + } + } else { + DEV_ASSERT(load_task.dependent_path.is_empty()); + } + // -- - thread_load_mutex->lock(); + Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); + + thread_load_mutex.lock(); + + load_task.resource = res; + + load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { load_task.status = THREAD_LOAD_LOADED; } - if (load_task.cond_var) { - if (load_task.start_next && thread_waiting_count > 0) { - thread_waiting_count--; - //thread loading count remains constant, this ends but another one begins - thread_load_semaphore->post(); - } else { - thread_loading_count--; //no threads waiting, just reduce loading count - } - - print_lt("END: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); + if (load_task.cond_var) { load_task.cond_var->notify_all(); memdelete(load_task.cond_var); load_task.cond_var = nullptr; } if (load_task.resource.is_valid()) { - load_task.resource->set_path(load_task.local_path); + if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + load_task.resource->set_path(load_task.local_path); + } if (load_task.xl_remapped) { load_task.resource->set_as_translation_remapped(true); } #ifdef TOOLS_ENABLED - load_task.resource->set_edited(false); if (timestamp_on_load) { uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path); @@ -286,7 +350,12 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { } } - thread_load_mutex->unlock(); + thread_load_mutex.unlock(); + + if (load_nesting == 0 && mq_override) { + memdelete(mq_override); + MessageQueue::set_thread_singleton_override(nullptr); + } } static String _validate_local_path(const String &p_path) { @@ -299,91 +368,127 @@ static String _validate_local_path(const String &p_path) { return ProjectSettings::get_singleton()->localize_path(p_path); } } -Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode, const String &p_source_resource) { - String local_path = _validate_local_path(p_path); - thread_load_mutex->lock(); +Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) { + thread_load_mutex.lock(); + if (user_load_tokens.has(p_path)) { + print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error."); + user_load_tokens[p_path]->reference(); // Additional request. + thread_load_mutex.unlock(); + return OK; + } + user_load_tokens[p_path] = nullptr; + thread_load_mutex.unlock(); + + Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode); + if (token.is_valid()) { + thread_load_mutex.lock(); + token->user_path = p_path; + token->reference(); // First request. + user_load_tokens[p_path] = token.ptr(); + print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size())); + thread_load_mutex.unlock(); + return OK; + } else { + return FAILED; + } +} - if (!p_source_resource.is_empty()) { - //must be loading from this resource - if (!thread_load_tasks.has(p_source_resource)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "There is no thread loading source resource '" + p_source_resource + "'."); - } - //must not be already added as s sub tasks - if (thread_load_tasks[p_source_resource].sub_tasks.has(local_path)) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Thread loading source resource '" + p_source_resource + "' already is loading '" + local_path + "'."); - } +Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { + if (r_error) { + *r_error = OK; } - if (thread_load_tasks.has(local_path)) { - thread_load_tasks[local_path].requests++; - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); + Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode); + if (!load_token.is_valid()) { + if (r_error) { + *r_error = FAILED; } - thread_load_mutex->unlock(); - return OK; + return Ref<Resource>(); } - { - //create load task - - ThreadLoadTask load_task; + Ref<Resource> res = _load_complete(*load_token.ptr(), r_error); + return res; +} - load_task.requests = 1; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.local_path = local_path; - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; - load_task.use_sub_threads = p_use_sub_threads; +Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { + String local_path = _validate_local_path(p_path); - { //must check if resource is already loaded before attempting to load it in a thread + Ref<LoadToken> load_token; + bool must_not_register = false; + ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. + ThreadLoadTask *load_task_ptr = nullptr; + bool run_on_current_thread = false; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.loader_id == Thread::get_caller_id()) { - thread_load_mutex->unlock(); - ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?"); + if (thread_load_tasks.has(local_path)) { + load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token); + if (!load_token.is_valid()) { + // The token is dying (reached 0 on another thread). + // Ensure it's killed now so the path can be safely reused right away. + thread_load_tasks[local_path].load_token->clear(); + } else { + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + return load_token; + } } + } - Ref<Resource> existing = ResourceCache::get_ref(local_path); + load_token.instantiate(); + load_token->local_path = local_path; - if (existing.is_valid()) { - //referencing is fine - load_task.resource = existing; - load_task.status = THREAD_LOAD_LOADED; - load_task.progress = 1.0; + //create load task + { + ThreadLoadTask load_task; + + load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); + load_task.load_token = load_token.ptr(); + load_task.local_path = local_path; + load_task.type_hint = p_type_hint; + load_task.cache_mode = p_cache_mode; + load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE; + if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + Ref<Resource> existing = ResourceCache::get_ref(local_path); + if (existing.is_valid()) { + //referencing is fine + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; + thread_load_tasks[local_path] = load_task; + return load_token; + } } - } - - if (!p_source_resource.is_empty()) { - thread_load_tasks[p_source_resource].sub_tasks.insert(local_path); - } - thread_load_tasks[local_path] = load_task; - } + // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously. + must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE; + if (must_not_register) { + load_token->local_path.clear(); + unregistered_load_task = load_task; + } else { + thread_load_tasks[local_path] = load_task; + } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path]; + } - if (load_task.resource.is_null()) { //needs to be loaded in thread + run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT; - load_task.cond_var = memnew(ConditionVariable); - if (thread_loading_count < thread_load_max) { - thread_loading_count++; - thread_load_semaphore->post(); //we have free threads, so allow one + if (run_on_current_thread) { + load_task_ptr->thread_id = Thread::get_caller_id(); + if (must_not_register) { + load_token->res_if_unregistered = load_task_ptr->resource; + } } else { - thread_waiting_count++; + load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr); } - - print_lt("REQUEST: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - - load_task.thread = memnew(Thread); - load_task.thread->start(_thread_load_function, &thread_load_tasks[local_path]); - load_task.loader_id = load_task.thread->get_id(); } - thread_load_mutex->unlock(); + if (run_on_current_thread) { + _thread_load_function(load_task_ptr); + } - return OK; + return load_token; } float ResourceLoader::_dependency_get_progress(const String &p_path) { @@ -409,13 +514,22 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - String local_path = _validate_local_path(p_path); + MutexLock thread_load_lock(thread_load_mutex); + + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - thread_load_mutex->lock(); + String local_path = _validate_local_path(p_path); if (!thread_load_tasks.has(local_path)) { - thread_load_mutex->unlock(); +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). return THREAD_LOAD_INVALID_RESOURCE; } + ThreadLoadTask &load_task = thread_load_tasks[local_path]; ThreadLoadStatus status; status = load_task.status; @@ -423,198 +537,120 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const *r_progress = _dependency_get_progress(local_path); } - thread_load_mutex->unlock(); - return status; } Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) { - String local_path = _validate_local_path(p_path); - - MutexLock thread_load_lock(*thread_load_mutex); - if (!thread_load_tasks.has(local_path)) { - if (r_error) { - *r_error = ERR_INVALID_PARAMETER; - } - return Ref<Resource>(); + if (r_error) { + *r_error = OK; } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; + Ref<Resource> res; + { + MutexLock thread_load_lock(thread_load_mutex); - if (load_task.status == THREAD_LOAD_IN_PROGRESS) { - if (load_task.loader_id == Thread::get_caller_id()) { - // Load is in progress, but it's precisely this thread the one in charge. - // That means this is a cyclic load. + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); if (r_error) { - *r_error = ERR_BUSY; + *r_error = ERR_INVALID_PARAMETER; } return Ref<Resource>(); - } else if (!load_task.cond_var) { - // Load is in progress, but a condition variable was never created for it. - // That happens when a load has been initiated with subthreads disabled, - // but now another load thread needs to interact with this one (either - // because of subthreads being used this time, or because it's simply a - // threaded load running on a different thread). - // Since we want to be notified when the load ends, we must create the - // condition variable now. - load_task.cond_var = memnew(ConditionVariable); } - } - - //cond var still exists, meaning it's still loading, request poll - if (load_task.cond_var) { - { - // As we got a cond var, this means we are going to have to wait - // until the sub-resource is done loading - // - // As this thread will become 'blocked' we should "exchange" its - // active status with a waiting one, to ensure load continues. - // - // This ensures loading is never blocked and that is also within - // the maximum number of active threads. - - if (thread_waiting_count > 0) { - thread_waiting_count--; - thread_loading_count++; - thread_load_semaphore->post(); - - load_task.start_next = false; //do not start next since we are doing it here - } - - thread_suspended_count++; - - print_lt("GET: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count)); - } - - bool still_valid = true; - bool was_thread = load_task.thread; - do { - load_task.cond_var->wait(thread_load_lock); - if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call - still_valid = false; - break; - } - } while (load_task.cond_var); // In case of spurious wakeup. - if (was_thread) { - thread_suspended_count--; - } - - if (!still_valid) { + LoadToken *load_token = user_load_tokens[p_path]; + if (!load_token) { + // This happens if requested from one thread and rapidly querying from another. if (r_error) { - *r_error = ERR_INVALID_PARAMETER; + *r_error = ERR_BUSY; } return Ref<Resource>(); } + res = _load_complete_inner(*load_token, r_error, thread_load_lock); + if (load_token->unreference()) { + memdelete(load_token); + } } - Ref<Resource> resource = load_task.resource; - if (r_error) { - *r_error = load_task.error; - } - - load_task.requests--; + print_lt("GET: user load tokens: " + itos(user_load_tokens.size())); - if (load_task.requests == 0) { - if (load_task.thread) { //thread may not have been used - load_task.thread->wait_to_finish(); - memdelete(load_task.thread); - } - thread_load_tasks.erase(local_path); - } + return res; +} - return resource; +Ref<Resource> ResourceLoader::_load_complete(LoadToken &p_load_token, Error *r_error) { + MutexLock thread_load_lock(thread_load_mutex); + return _load_complete_inner(p_load_token, r_error, thread_load_lock); } -Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) { +Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock) { if (r_error) { - *r_error = ERR_CANT_OPEN; + *r_error = OK; } - String local_path = _validate_local_path(p_path); + if (!p_load_token.local_path.is_empty()) { + if (!thread_load_tasks.has(p_load_token.local_path)) { +#ifdef DEV_ENABLED + CRASH_NOW(); +#endif + // On non-dev, be defensive and at least avoid crashing (at this point at least). + if (r_error) { + *r_error = ERR_BUG; + } + return Ref<Resource>(); + } - if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - thread_load_mutex->lock(); + ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path]; - //Is it already being loaded? poll until done - if (thread_load_tasks.has(local_path)) { - Error err = load_threaded_request(p_path, p_type_hint); - if (err != OK) { + if (load_task.status == THREAD_LOAD_IN_PROGRESS) { + DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0)); + + if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) || + (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) { + // Load is in progress, but it's precisely this thread the one in charge. + // That means this is a cyclic load. if (r_error) { - *r_error = err; + *r_error = ERR_BUSY; } - thread_load_mutex->unlock(); return Ref<Resource>(); } - thread_load_mutex->unlock(); - return load_threaded_get(p_path, r_error); - } - - //Is it cached? - - Ref<Resource> existing = ResourceCache::get_ref(local_path); - - if (existing.is_valid()) { - thread_load_mutex->unlock(); - - if (r_error) { - *r_error = OK; + if (load_task.task_id != 0 && !load_task.awaited) { + // Loading thread is in the worker pool and still not awaited. + load_task.awaited = true; + thread_load_mutex.unlock(); + WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + thread_load_mutex.lock(); + } else { + // Loading thread is main or user thread, or in the worker pool, but already awaited by some other thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); + } + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.cond_var); } - - return existing; //use cached } - //load using task (but this thread) - ThreadLoadTask load_task; - - load_task.requests = 1; - load_task.local_path = local_path; - load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped); - load_task.type_hint = p_type_hint; - load_task.cache_mode = p_cache_mode; //ignore - load_task.loader_id = Thread::get_caller_id(); - - thread_load_tasks[local_path] = load_task; - - thread_load_mutex->unlock(); - - _thread_load_function(&thread_load_tasks[local_path]); - - return load_threaded_get(p_path, r_error); - - } else { - bool xl_remapped = false; - String path = _path_remap(local_path, &xl_remapped); - - if (path.is_empty()) { - ERR_FAIL_V_MSG(Ref<Resource>(), "Remapping '" + local_path + "' failed."); - } - - print_verbose("Loading resource: " + path); - float p; - Ref<Resource> res = _load(path, local_path, p_type_hint, p_cache_mode, r_error, false, &p); - - if (res.is_null()) { - print_verbose("Failed loading resource: " + path); - return Ref<Resource>(); + if (cleaning_tasks) { + load_task.resource = Ref<Resource>(); + load_task.error = FAILED; } - if (xl_remapped) { - res->set_as_translation_remapped(true); + Ref<Resource> resource = load_task.resource; + if (r_error) { + *r_error = load_task.error; } - -#ifdef TOOLS_ENABLED - - res->set_edited(false); - if (timestamp_on_load) { - uint64_t mt = FileAccess::get_modified_time(path); - //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt); - res->set_last_modified_time(mt); + return resource; + } else { + // Special case of an unregistered task. + // The resource should have been loaded by now. + Ref<Resource> resource = p_load_token.res_if_unregistered; + if (!resource.is_valid()) { + if (r_error) { + *r_error = FAILED; + } } -#endif - - return res; + return resource; } } @@ -958,32 +994,42 @@ void ResourceLoader::clear_translation_remaps() { } void ResourceLoader::clear_thread_load_tasks() { - thread_load_mutex->lock(); - - for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { - switch (E.value.status) { - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED: { - E.value.resource = Ref<Resource>(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS: { - if (E.value.thread != nullptr) { - E.value.thread->wait_to_finish(); - memdelete(E.value.thread); - E.value.thread = nullptr; + // Bring the thing down as quickly as possible without causing deadlocks or leaks. + + thread_load_mutex.lock(); + cleaning_tasks = true; + + while (true) { + bool none_running = true; + if (thread_load_tasks.size()) { + for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) { + if (E.value.status == THREAD_LOAD_IN_PROGRESS) { + if (E.value.cond_var) { + E.value.cond_var->notify_all(); + memdelete(E.value.cond_var); + E.value.cond_var = nullptr; + } + none_running = false; } - E.value.resource = Ref<Resource>(); - } break; - - case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED: - default: { - // do nothing } } + if (none_running) { + break; + } + thread_load_mutex.unlock(); + OS::get_singleton()->delay_usec(1000); + thread_load_mutex.lock(); + } + + for (KeyValue<String, LoadToken *> &E : user_load_tokens) { + memdelete(E.value); } + user_load_tokens.clear(); + thread_load_tasks.clear(); - thread_load_mutex->unlock(); + cleaning_tasks = false; + thread_load_mutex.unlock(); } void ResourceLoader::load_path_remaps() { @@ -1080,20 +1126,14 @@ void ResourceLoader::remove_custom_loaders() { } } -void ResourceLoader::initialize() { - thread_load_mutex = memnew(SafeBinaryMutex<BINARY_MUTEX_TAG>); - thread_load_max = OS::get_singleton()->get_processor_count(); - thread_loading_count = 0; - thread_waiting_count = 0; - thread_suspended_count = 0; - thread_load_semaphore = memnew(Semaphore); +bool ResourceLoader::is_cleaning_tasks() { + MutexLock lock(thread_load_mutex); + return cleaning_tasks; } -void ResourceLoader::finalize() { - clear_thread_load_tasks(); - memdelete(thread_load_mutex); - memdelete(thread_load_semaphore); -} +void ResourceLoader::initialize() {} + +void ResourceLoader::finalize() {} ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr; void *ResourceLoader::err_notify_ud = nullptr; @@ -1105,16 +1145,17 @@ bool ResourceLoader::create_missing_resources_if_class_unavailable = false; bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; +thread_local int ResourceLoader::load_nesting = 0; +thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; +thread_local Vector<String> ResourceLoader::load_paths_stack; + template <> thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0; -SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> *ResourceLoader::thread_load_mutex = nullptr; +SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex; HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks; -Semaphore *ResourceLoader::thread_load_semaphore = nullptr; +bool ResourceLoader::cleaning_tasks = false; -int ResourceLoader::thread_loading_count = 0; -int ResourceLoader::thread_waiting_count = 0; -int ResourceLoader::thread_suspended_count = 0; -int ResourceLoader::thread_load_max = 0; +HashMap<String, ResourceLoader::LoadToken *> ResourceLoader::user_load_tokens; SelfList<Resource>::List ResourceLoader::remapped_list; HashMap<String, Vector<String>> ResourceLoader::translation_remaps; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 72c1f90653..ffe9d5de9a 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "core/object/gdvirtual.gen.inc" #include "core/object/script_language.h" +#include "core/object/worker_thread_pool.h" #include "core/os/semaphore.h" #include "core/os/thread.h" @@ -107,9 +108,30 @@ public: THREAD_LOAD_LOADED }; + enum LoadThreadMode { + LOAD_THREAD_FROM_CURRENT, + LOAD_THREAD_SPAWN_SINGLE, + LOAD_THREAD_DISTRIBUTE, + }; + + struct LoadToken : public RefCounted { + String local_path; + String user_path; + Ref<Resource> res_if_unregistered; + + void clear(); + + virtual ~LoadToken(); + }; + static const int BINARY_MUTEX_TAG = 1; + static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode); + static Ref<Resource> _load_complete(LoadToken &p_load_token, Error *r_error); + private: + static Ref<Resource> _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock); + static Ref<ResourceFormatLoader> loader[MAX_LOADERS]; static int loader_count; static bool timestamp_on_load; @@ -129,8 +151,7 @@ private: static SelfList<Resource>::List remapped_list; friend class ResourceFormatImporter; - friend class ResourceInteractiveLoader; - // Internal load function. + static Ref<Resource> _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress); static ResourceLoadedCallback _loaded_callback; @@ -138,11 +159,14 @@ private: static Ref<ResourceFormatLoader> _find_custom_resource_format_loader(String path); struct ThreadLoadTask { - Thread *thread = nullptr; - Thread::ID loader_id = 0; - ConditionVariable *cond_var = nullptr; + WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool. + Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load). + bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread. + ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism. + LoadToken *load_token = nullptr; String local_path; String remapped_path; + String dependent_path; String type_hint; float progress = 0.0; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; @@ -151,27 +175,29 @@ private: Ref<Resource> resource; bool xl_remapped = false; bool use_sub_threads = false; - bool start_next = true; - int requests = 0; HashSet<String> sub_tasks; }; static void _thread_load_function(void *p_userdata); - static SafeBinaryMutex<BINARY_MUTEX_TAG> *thread_load_mutex; + + static thread_local int load_nesting; + static thread_local WorkerThreadPool::TaskID caller_task_id; + static thread_local Vector<String> load_paths_stack; + static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex; static HashMap<String, ThreadLoadTask> thread_load_tasks; - static Semaphore *thread_load_semaphore; - static int thread_waiting_count; - static int thread_loading_count; - static int thread_suspended_count; - static int thread_load_max; + static bool cleaning_tasks; + + static HashMap<String, LoadToken *> user_load_tokens; static float _dependency_get_progress(const String &p_path); public: - static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, const String &p_source_resource = String()); + static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); static Ref<Resource> load_threaded_get(const String &p_path, Error *r_error = nullptr); + static bool is_within_load() { return load_nesting > 0; }; + static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr); static bool exists(const String &p_path, const String &p_type_hint = ""); @@ -237,6 +263,8 @@ public: static void set_create_missing_resources_if_class_unavailable(bool p_enable); _FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; } + static bool is_cleaning_tasks(); + static void initialize(); static void finalize(); }; diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 05f4e2a8a6..55ea5f5ecd 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,14 +35,23 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" +#define LOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.lock(); \ + } + +#define UNLOCK_MUTEX \ + if (this != MessageQueue::thread_singleton) { \ + mutex.unlock(); \ + } + void CallQueue::_add_page() { - if (pages_used == page_messages.size()) { + if (pages_used == page_bytes.size()) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); } - page_messages[pages_used] = 0; + page_bytes[pages_used] = 0; pages_used++; - page_offset = 0; } Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) { @@ -66,15 +75,15 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments."); - mutex.lock(); + LOCK_MUTEX; _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); @@ -82,7 +91,7 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = p_argcount; @@ -104,21 +113,20 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar *v = *p_args[i]; } - page_messages[pages_used - 1]++; - page_offset += room_needed; + page_bytes[pages_used - 1] += room_needed; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) { - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message) + sizeof(Variant); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { String type; if (ObjectDB::get_instance(p_id)) { @@ -127,14 +135,14 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); msg->args = 1; @@ -146,32 +154,31 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant Variant *v = memnew_placement(buffer_end, Variant); *v = p_value; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } Error CallQueue::push_notification(ObjectID p_id, int p_notification) { ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER); - mutex.lock(); + LOCK_MUTEX; uint32_t room_needed = sizeof(Message); _ensure_first_page(); - if ((page_offset + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { + if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text); statistics(); - mutex.unlock(); + UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; } _add_page(); } Page *page = pages[pages_used - 1]; - uint8_t *buffer_end = &page->data[page_offset]; + uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]]; Message *msg = memnew_placement(buffer_end, Message); @@ -180,9 +187,8 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) { //msg->target; msg->notification = p_notification; - page_messages[pages_used - 1]++; - page_offset += room_needed; - mutex.unlock(); + page_bytes[pages_used - 1] += room_needed; + UNLOCK_MUTEX; return OK; } @@ -205,26 +211,77 @@ void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args } Error CallQueue::flush() { - mutex.lock(); + LOCK_MUTEX; + + // Non-main threads are not meant to be flushed, but appended to the main one. + if (this != MessageQueue::main_singleton) { + if (pages.size() == 0) { + return OK; + } + + CallQueue *mq = MessageQueue::main_singleton; + DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters. + + mq->mutex.lock(); + + // Here we're transferring the data from this queue to the main one. + // However, it's very unlikely big amounts of messages will be queued here, + // so PagedArray/Pool would be overkill. Also, in most cases the data will fit + // an already existing page of the main queue. + + // Let's see if our first (likely only) page fits the current target queue page. + uint32_t src_page = 0; + { + if (mq->pages_used) { + uint32_t dst_page = mq->pages_used - 1; + uint32_t dst_offset = mq->page_bytes[dst_page]; + if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) { + memcpy(mq->pages[dst_page] + dst_offset, pages[0], page_bytes[0]); + src_page++; + } + } + } + + // Any other possibly existing source page needs to be added. + + if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { + ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); + mq->statistics(); + mq->mutex.unlock(); + return ERR_OUT_OF_MEMORY; + } + + for (; src_page < pages_used; src_page++) { + mq->_add_page(); + memcpy(mq->pages[mq->pages_used - 1], pages[src_page], page_bytes[src_page]); + mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page]; + } + + mq->mutex.unlock(); + + page_bytes[0] = 0; + pages_used = 1; + + return OK; + } if (pages.size() == 0) { // Never allocated - mutex.unlock(); + UNLOCK_MUTEX; return OK; // Do nothing. } if (flushing) { - mutex.unlock(); + UNLOCK_MUTEX; return ERR_BUSY; } flushing = true; uint32_t i = 0; - uint32_t j = 0; uint32_t offset = 0; - while (i < pages_used && j < page_messages[i]) { + while (i < pages_used && offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -241,7 +298,7 @@ Error CallQueue::flush() { Object *target = message->callable.get_object(); - mutex.unlock(); + UNLOCK_MUTEX; switch (message->type & FLAG_MASK) { case TYPE_CALL: { @@ -272,35 +329,32 @@ Error CallQueue::flush() { message->~Message(); - mutex.lock(); - j++; - if (j == page_messages[i]) { - j = 0; + LOCK_MUTEX; + if (offset == page_bytes[i]) { i++; offset = 0; } } - page_messages[0] = 0; - page_offset = 0; + page_bytes[0] = 0; pages_used = 1; flushing = false; - mutex.unlock(); + UNLOCK_MUTEX; return OK; } void CallQueue::clear() { - mutex.lock(); + LOCK_MUTEX; if (pages.size() == 0) { - mutex.unlock(); + UNLOCK_MUTEX; return; // Nothing to clear. } for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -312,7 +366,6 @@ void CallQueue::clear() { advance += sizeof(Variant) * message->args; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -327,14 +380,13 @@ void CallQueue::clear() { } pages_used = 1; - page_offset = 0; - page_messages[0] = 0; + page_bytes[0] = 0; - mutex.unlock(); + UNLOCK_MUTEX; } void CallQueue::statistics() { - mutex.lock(); + LOCK_MUTEX; HashMap<StringName, int> set_count; HashMap<int, int> notify_count; HashMap<Callable, int> call_count; @@ -342,7 +394,7 @@ void CallQueue::statistics() { for (uint32_t i = 0; i < pages_used; i++) { uint32_t offset = 0; - for (uint32_t j = 0; j < page_messages[i]; j++) { + while (offset < page_bytes[i]) { Page *page = pages[i]; //lock on each iteration, so a call can re-add itself to the message queue @@ -397,7 +449,6 @@ void CallQueue::statistics() { null_count++; } - //pre-advance so this function is reentrant offset += advance; if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) { @@ -426,7 +477,7 @@ void CallQueue::statistics() { print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); } - mutex.unlock(); + UNLOCK_MUTEX; } bool CallQueue::is_flushing() const { @@ -437,7 +488,7 @@ bool CallQueue::has_messages() const { if (pages_used == 0) { return false; } - if (pages_used == 1 && page_messages[0] == 0) { + if (pages_used == 1 && page_bytes[0] == 0) { return false; } @@ -473,16 +524,21 @@ CallQueue::~CallQueue() { ////////////////////// -MessageQueue *MessageQueue::singleton = nullptr; +CallQueue *MessageQueue::main_singleton = nullptr; +thread_local CallQueue *MessageQueue::thread_singleton = nullptr; + +void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) { + thread_singleton = p_thread_singleton; +} MessageQueue::MessageQueue() : CallQueue(nullptr, int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES, "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") { - ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists."); - singleton = this; + ERR_FAIL_COND_MSG(main_singleton != nullptr, "A MessageQueue singleton already exists."); + main_singleton = this; } MessageQueue::~MessageQueue() { - singleton = nullptr; + main_singleton = nullptr; } diff --git a/core/object/message_queue.h b/core/object/message_queue.h index fe261f840e..c6fcccbd58 100644 --- a/core/object/message_queue.h +++ b/core/object/message_queue.h @@ -70,10 +70,9 @@ private: bool allocator_is_custom = false; LocalVector<Page *> pages; - LocalVector<uint32_t> page_messages; + LocalVector<uint32_t> page_bytes; uint32_t max_pages = 0; uint32_t pages_used = 0; - uint32_t page_offset = 0; bool flushing = false; struct Message { @@ -88,7 +87,7 @@ private: _FORCE_INLINE_ void _ensure_first_page() { if (unlikely(pages.is_empty())) { pages.push_back(allocator->alloc()); - page_messages.push_back(0); + page_bytes.push_back(0); pages_used = 1; } } @@ -153,10 +152,15 @@ public: }; class MessageQueue : public CallQueue { - static MessageQueue *singleton; + static CallQueue *main_singleton; + static thread_local CallQueue *thread_singleton; + friend class CallQueue; public: - _FORCE_INLINE_ static MessageQueue *get_singleton() { return singleton; } + _FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; } + + static void set_thread_singleton_override(CallQueue *p_thread_singleton); + MessageQueue(); ~MessageQueue(); }; diff --git a/core/os/mutex.h b/core/os/mutex.h index 90cc1632e8..cee0f8af74 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -119,8 +119,25 @@ class MutexLock { public: _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : + lock(p_mutex.mutex){}; +}; + +// This specialization is needed so manual locking and MutexLock can be used +// at the same time on a SafeBinaryMutex. +template <int Tag> +class MutexLock<SafeBinaryMutex<Tag>> { + friend class ConditionVariable; + + std::unique_lock<std::mutex> lock; + +public: + _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : lock(p_mutex.mutex) { - } + SafeBinaryMutex<Tag>::count++; + }; + _ALWAYS_INLINE_ ~MutexLock() { + SafeBinaryMutex<Tag>::count--; + }; }; using Mutex = MutexImpl<std::recursive_mutex>; // Recursive, for general use diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 502f82aaef..c067ad1a6a 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -66,11 +66,12 @@ void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_cal } } -void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) { - ERR_FAIL_COND_MSG(id != UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it."); +Thread::ID Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) { + ERR_FAIL_COND_V_MSG(id != UNASSIGNED_ID, UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it."); id = id_counter.increment(); std::thread new_thread(&Thread::callback, id, p_settings, p_callback, p_user); thread.swap(new_thread); + return id; } bool Thread::is_started() const { diff --git a/core/os/thread.h b/core/os/thread.h index a769bb1df4..3e307adfff 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -109,7 +109,7 @@ public: static Error set_name(const String &p_name); - void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()); + ID start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings()); bool is_started() const; ///< waits until thread is finished, and deallocates it. void wait_to_finish(); diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index b45e03dc38..6a27bff763 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -416,6 +416,9 @@ <member name="editors/polygon_editor/show_previous_outline" type="bool" setter="" getter=""> If [code]true[/code], displays the polygon's previous shape in the 2D polygon editors with an opaque gray outline. This outline is displayed while dragging a point until the left mouse button is released. </member> + <member name="editors/shader_editor/behavior/files/restore_shaders_on_load" type="bool" setter="" getter=""> + If [code]true[/code], reopens shader files that were open in the shader editor when the project was last closed. + </member> <member name="editors/tiles_editor/display_grid" type="bool" setter="" getter=""> If [code]true[/code], displays a grid while the TileMap editor is active. See also [member editors/tiles_editor/grid_color]. </member> diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml index 2a3cba3a44..afe0088a26 100644 --- a/doc/classes/Tree.xml +++ b/doc/classes/Tree.xml @@ -60,8 +60,11 @@ </method> <method name="edit_selected"> <return type="bool" /> + <param index="0" name="force_edit" type="bool" default="false" /> <description> - Edits the selected tree item as if it was clicked. The item must be set editable with [method TreeItem.set_editable]. Returns [code]true[/code] if the item could be edited. Fails if no item is selected. + Edits the selected tree item as if it was clicked. + Either the item must be set editable with [method TreeItem.set_editable] or [param force_edit] must be [code]true[/code]. + Returns [code]true[/code] if the item could be edited. Fails if no item is selected. </description> </method> <method name="ensure_cursor_is_visible"> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index b97b4bf17d..6b6851daf1 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -226,11 +226,19 @@ Returns the next sibling TreeItem in the tree or a null object if there is none. </description> </method> + <method name="get_next_in_tree"> + <return type="TreeItem" /> + <param index="0" name="wrap" type="bool" default="false" /> + <description> + Returns the next TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. + If [param wrap] is enabled, the method will wrap around to the first element in the tree when called on the last element, otherwise it returns [code]null[/code]. + </description> + </method> <method name="get_next_visible"> <return type="TreeItem" /> <param index="0" name="wrap" type="bool" default="false" /> <description> - Returns the next visible sibling TreeItem in the tree or a null object if there is none. + Returns the next visible TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. If [param wrap] is enabled, the method will wrap around to the first visible element in the tree when called on the last visible element, otherwise it returns [code]null[/code]. </description> </method> @@ -246,11 +254,19 @@ Returns the previous sibling TreeItem in the tree or a null object if there is none. </description> </method> + <method name="get_prev_in_tree"> + <return type="TreeItem" /> + <param index="0" name="wrap" type="bool" default="false" /> + <description> + Returns the previous TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. + If [param wrap] is enabled, the method will wrap around to the last element in the tree when called on the first visible element, otherwise it returns [code]null[/code]. + </description> + </method> <method name="get_prev_visible"> <return type="TreeItem" /> <param index="0" name="wrap" type="bool" default="false" /> <description> - Returns the previous visible sibling TreeItem in the tree or a null object if there is none. + Returns the previous visible sibling TreeItem in the tree (in the context of a depth-first search) or a [code]null[/code] object if there is none. If [param wrap] is enabled, the method will wrap around to the last visible element in the tree when called on the first visible element, otherwise it returns [code]null[/code]. </description> </method> diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index a696e1ff1f..b4ca499bcf 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1704,6 +1704,14 @@ void ScriptEditorDebugger::remove_debugger_tab(Control *p_control) { p_control->queue_free(); } +int ScriptEditorDebugger::get_current_debugger_tab() const { + return tabs->get_current_tab(); +} + +void ScriptEditorDebugger::switch_to_debugger(int p_debugger_tab_idx) { + tabs->set_current_tab(p_debugger_tab_idx); +} + void ScriptEditorDebugger::send_message(const String &p_message, const Array &p_args) { _put_msg(p_message, p_args); } diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h index 1659bbee8d..336a113163 100644 --- a/editor/debugger/script_editor_debugger.h +++ b/editor/debugger/script_editor_debugger.h @@ -284,6 +284,8 @@ public: void add_debugger_tab(Control *p_control); void remove_debugger_tab(Control *p_control); + int get_current_debugger_tab() const; + void switch_to_debugger(int p_debugger_tab_idx); void send_message(const String &p_message, const Array &p_args); void toggle_profiler(const String &p_profiler, bool p_enable, const Array &p_data); diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 753f54b807..5d3037b4ec 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -300,7 +300,7 @@ void EditorData::get_editor_breakpoints(List<String> *p_breakpoints) { } } -Dictionary EditorData::get_editor_states() const { +Dictionary EditorData::get_editor_plugin_states() const { Dictionary metadata; for (int i = 0; i < editor_plugins.size(); i++) { Dictionary state = editor_plugins[i]->get_state(); @@ -319,7 +319,7 @@ Dictionary EditorData::get_scene_editor_states(int p_idx) const { return es.editor_states; } -void EditorData::set_editor_states(const Dictionary &p_states) { +void EditorData::set_editor_plugin_states(const Dictionary &p_states) { if (p_states.is_empty()) { for (EditorPlugin *ep : editor_plugins) { ep->clear(); @@ -891,7 +891,7 @@ void EditorData::save_edited_scene_state(EditorSelection *p_selection, EditorSel es.selection = p_selection->get_full_selected_node_list(); es.history_current = p_history->current_elem_idx; es.history_stored = p_history->history; - es.editor_states = get_editor_states(); + es.editor_states = get_editor_plugin_states(); es.custom_state = p_custom; } @@ -907,7 +907,7 @@ Dictionary EditorData::restore_edited_scene_state(EditorSelection *p_selection, for (Node *E : es.selection) { p_selection->add_node(E); } - set_editor_states(es.editor_states); + set_editor_plugin_states(es.editor_states); return es.custom_state; } diff --git a/editor/editor_data.h b/editor/editor_data.h index d4a2f534cd..7ca04b5680 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -156,9 +156,9 @@ public: void copy_object_params(Object *p_object); void paste_object_params(Object *p_object); - Dictionary get_editor_states() const; + Dictionary get_editor_plugin_states() const; Dictionary get_scene_editor_states(int p_idx) const; - void set_editor_states(const Dictionary &p_states); + void set_editor_plugin_states(const Dictionary &p_states); void get_editor_breakpoints(List<String> *p_breakpoints); void clear_editor_states(); void save_editor_external_data(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 7c85878d28..6f763d59f4 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -67,6 +67,7 @@ #include "editor/audio_stream_preview.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/dependency_editor.h" #include "editor/editor_about.h" #include "editor/editor_audio_buses.h" @@ -157,6 +158,8 @@ EditorNode *EditorNode::singleton = nullptr; // The metadata key used to store and retrieve the version text to copy to the clipboard. static const String META_TEXT_TO_COPY = "text_to_copy"; +static const String EDITOR_NODE_CONFIG_SECTION = "EditorNode"; + class AcceptDialogAutoReparent : public AcceptDialog { GDCLASS(AcceptDialogAutoReparent, AcceptDialog); @@ -1094,7 +1097,7 @@ void EditorNode::_sources_changed(bool p_exist) { EditorResourcePreview::get_singleton()->start(); } - _load_docks(); + _load_editor_layout(); if (!defer_load_scene.is_empty()) { Engine::get_singleton()->startup_benchmark_begin_measure("editor_load_scene"); @@ -1463,39 +1466,28 @@ void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { } } -void EditorNode::_get_scene_metadata(const String &p_file) { +void EditorNode::_load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file) { Node *scene = editor_data.get_edited_scene_root(); if (!scene) { return; } - String path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_file.get_file() + "-editstate-" + p_file.md5_text() + ".cfg"); - - Ref<ConfigFile> cf; - cf.instantiate(); - - Error err = cf->load(path); - if (err != OK || !cf->has_section("editor_states")) { - // Must not exist. - return; - } - List<String> esl; - cf->get_section_keys("editor_states", &esl); + p_config_file->get_section_keys("editor_states", &esl); Dictionary md; for (const String &E : esl) { - Variant st = cf->get_value("editor_states", E); + Variant st = p_config_file->get_value("editor_states", E); if (st.get_type() != Variant::NIL) { md[E] = st; } } - editor_data.set_editor_states(md); + editor_data.set_editor_plugin_states(md); } -void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) { +void EditorNode::_save_editor_states(const String &p_file, int p_idx) { Node *scene = editor_data.get_edited_scene_root(p_idx); if (!scene) { @@ -1508,20 +1500,27 @@ void EditorNode::_set_scene_metadata(const String &p_file, int p_idx) { cf.instantiate(); Dictionary md; - if (p_idx < 0 || editor_data.get_edited_scene() == p_idx) { - md = editor_data.get_editor_states(); + md = editor_data.get_editor_plugin_states(); } else { md = editor_data.get_scene_editor_states(p_idx); } List<Variant> keys; md.get_key_list(&keys); - for (const Variant &E : keys) { cf->set_value("editor_states", E, md[E]); } + // Save the currently selected nodes. + + List<Node *> selection = editor_selection->get_full_selected_node_list(); + TypedArray<NodePath> selection_paths; + for (Node *selected_node : selection) { + selection_paths.push_back(selected_node->get_path()); + } + cf->set_value("editor_states", "selected_nodes", selection_paths); + Error err = cf->save(path); ERR_FAIL_COND_MSG(err != OK, "Cannot save config file to '" + path + "'."); } @@ -1813,7 +1812,7 @@ void EditorNode::_save_scene(String p_file, int idx) { _reset_animation_players(scene, &anim_backups); save_default_environment(); - _set_scene_metadata(p_file, idx); + _save_editor_states(p_file, idx); Ref<PackedScene> sdata; @@ -2025,7 +2024,7 @@ void EditorNode::_dialog_action(String p_file) { save_default_environment(); _save_scene_with_preview(p_file, scene_idx); _add_to_recent_scenes(p_file); - save_layout(); + save_editor_layout_delayed(); if (scene_idx != -1) { _discard_changes(); @@ -2605,7 +2604,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (scene_idx != -1) { _discard_changes(); } - save_layout(); + save_editor_layout_delayed(); } else { show_save_accept(vformat(TTR("%s no longer exists! Please specify a new save location."), scene->get_scene_file_path().get_base_dir()), TTR("OK")); } @@ -3084,7 +3083,7 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) { void EditorNode::_exit_editor(int p_exit_code) { exiting = true; resource_preview->stop(); // Stop early to avoid crashes. - _save_docks(); + _save_editor_layout(); // Dim the editor window while it's quitting to make it clearer that it's busy. dim_editor(true); @@ -3753,7 +3752,14 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b new_scene->set_scene_instance_state(Ref<SceneState>()); set_edited_scene(new_scene); - _get_scene_metadata(p_scene); + + String config_file_path = EditorPaths::get_singleton()->get_project_settings_dir().path_join(p_scene.get_file() + "-editstate-" + p_scene.md5_text() + ".cfg"); + Ref<ConfigFile> editor_state_cf; + editor_state_cf.instantiate(); + Error editor_state_cf_err = editor_state_cf->load(config_file_path); + if (editor_state_cf_err == OK || editor_state_cf->has_section("editor_states")) { + _load_editor_plugin_states_from_config(editor_state_cf); + } _update_title(); _update_scene_tabs(); @@ -3774,8 +3780,20 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b push_item(new_scene); + // Load the selected nodes. + if (editor_state_cf->has_section_key("editor_states", "selected_nodes")) { + TypedArray<NodePath> selected_node_list = editor_state_cf->get_value("editor_states", "selected_nodes", TypedArray<String>()); + + for (int i = 0; i < selected_node_list.size(); i++) { + Node *selected_node = new_scene->get_node_or_null(selected_node_list[i]); + if (selected_node) { + editor_selection->add_node(selected_node); + } + } + } + if (!restoring_scenes) { - save_layout(); + save_editor_layout_delayed(); } return OK; @@ -4609,7 +4627,7 @@ void EditorNode::_dock_select_input(const Ref<InputEvent> &p_input) { _update_dock_containers(); _edit_current(); - _save_docks(); + _save_editor_layout(); } } } @@ -4635,7 +4653,7 @@ void EditorNode::_dock_move_left() { dock_slot[dock_popup_selected_idx]->move_child(current_ctl, prev_ctl->get_index(false)); dock_select->queue_redraw(); _edit_current(); - _save_docks(); + _save_editor_layout(); } void EditorNode::_dock_move_right() { @@ -4647,7 +4665,7 @@ void EditorNode::_dock_move_right() { dock_slot[dock_popup_selected_idx]->move_child(next_ctl, current_ctl->get_index(false)); dock_select->queue_redraw(); _edit_current(); - _save_docks(); + _save_editor_layout(); } void EditorNode::_dock_select_draw() { @@ -4736,7 +4754,7 @@ void EditorNode::_dock_select_draw() { } } -void EditorNode::_save_docks() { +void EditorNode::_save_editor_layout() { if (waiting_for_first_scan) { return; // Scanning, do not touch docks. } @@ -4746,7 +4764,8 @@ void EditorNode::_save_docks() { config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); _save_docks_to_config(config, "docks"); - _save_open_scenes_to_config(config, "EditorNode"); + _save_open_scenes_to_config(config); + _save_central_editor_layout_to_config(config); editor_data.get_plugin_window_layout(config); config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); @@ -4772,6 +4791,11 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p if (!names.is_empty()) { p_layout->set_value(p_section, config_key, names); } + + int selected_tab_idx = dock_slot[i]->get_current_tab(); + if (selected_tab_idx >= 0) { + p_layout->set_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx", selected_tab_idx); + } } Dictionary floating_docks_dump; @@ -4803,11 +4827,6 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p p_layout->set_value(p_section, "dock_floating", floating_docks_dump); - p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_split_offset()); - p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode()); - p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort()); - p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode()); - for (int i = 0; i < vsplits.size(); i++) { if (vsplits[i]->is_visible_in_tree()) { p_layout->set_value(p_section, "dock_split_" + itos(i + 1), vsplits[i]->get_split_offset()); @@ -4817,10 +4836,21 @@ void EditorNode::_save_docks_to_config(Ref<ConfigFile> p_layout, const String &p for (int i = 0; i < hsplits.size(); i++) { p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset()); } + + // Save FileSystemDock state. + + p_layout->set_value(p_section, "dock_filesystem_split", FileSystemDock::get_singleton()->get_split_offset()); + p_layout->set_value(p_section, "dock_filesystem_display_mode", FileSystemDock::get_singleton()->get_display_mode()); + p_layout->set_value(p_section, "dock_filesystem_file_sort", FileSystemDock::get_singleton()->get_file_sort()); + p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", FileSystemDock::get_singleton()->get_file_list_display_mode()); + PackedStringArray selected_files = FileSystemDock::get_singleton()->get_selected_paths(); + p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files); + Vector<String> uncollapsed_paths = FileSystemDock::get_singleton()->get_uncollapsed_paths(); + p_layout->set_value(p_section, "dock_filesystem_uncollapsed_paths", uncollapsed_paths); } -void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section) { - Array scenes; +void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout) { + PackedStringArray scenes; for (int i = 0; i < editor_data.get_edited_scene_count(); i++) { String path = editor_data.get_scene_path(i); if (path.is_empty()) { @@ -4828,18 +4858,21 @@ void EditorNode::_save_open_scenes_to_config(Ref<ConfigFile> p_layout, const Str } scenes.push_back(path); } - p_layout->set_value(p_section, "open_scenes", scenes); + p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes", scenes); + + String currently_edited_scene_path = editor_data.get_scene_path(editor_data.get_edited_scene()); + p_layout->set_value(EDITOR_NODE_CONFIG_SECTION, "current_scene", currently_edited_scene_path); } -void EditorNode::save_layout() { - dock_drag_timer->start(); +void EditorNode::save_editor_layout_delayed() { + editor_layout_save_delay_timer->start(); } void EditorNode::_dock_split_dragged(int ofs) { - dock_drag_timer->start(); + editor_layout_save_delay_timer->start(); } -void EditorNode::_load_docks() { +void EditorNode::_load_editor_layout() { Ref<ConfigFile> config; config.instantiate(); Error err = config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg")); @@ -4852,7 +4885,8 @@ void EditorNode::_load_docks() { } _load_docks_from_config(config, "docks"); - _load_open_scenes_from_config(config, "EditorNode"); + _load_open_scenes_from_config(config); + _load_central_editor_layout_from_config(config); editor_data.set_plugin_window_layout(config); } @@ -5031,26 +5065,15 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String _dock_floating_close_request(wrapper); } } - } - - if (p_layout->has_section_key(p_section, "dock_filesystem_split")) { - int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split"); - FileSystemDock::get_singleton()->set_split_offset(fs_split_ofs); - } - - if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) { - FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode"))); - FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode); - } - if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) { - FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort"))); - FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort); - } + if (!p_layout->has_section_key(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx")) { + continue; + } - if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) { - FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode"))); - FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode); + int selected_tab_idx = p_layout->get_value(p_section, "dock_" + itos(i + 1) + "_selected_tab_idx"); + if (selected_tab_idx >= 0 && selected_tab_idx < dock_slot[i]->get_tab_count()) { + dock_slot[i]->call_deferred("set_current_tab", selected_tab_idx); + } } for (int i = 0; i < vsplits.size(); i++) { @@ -5090,24 +5113,141 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String dock_slot[i]->set_current_tab(0); } } + + // FileSystemDock. + + if (p_layout->has_section_key(p_section, "dock_filesystem_split")) { + int fs_split_ofs = p_layout->get_value(p_section, "dock_filesystem_split"); + FileSystemDock::get_singleton()->set_split_offset(fs_split_ofs); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_display_mode")) { + FileSystemDock::DisplayMode dock_filesystem_display_mode = FileSystemDock::DisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_display_mode"))); + FileSystemDock::get_singleton()->set_display_mode(dock_filesystem_display_mode); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_file_sort")) { + FileSystemDock::FileSortOption dock_filesystem_file_sort = FileSystemDock::FileSortOption(int(p_layout->get_value(p_section, "dock_filesystem_file_sort"))); + FileSystemDock::get_singleton()->set_file_sort(dock_filesystem_file_sort); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_file_list_display_mode")) { + FileSystemDock::FileListDisplayMode dock_filesystem_file_list_display_mode = FileSystemDock::FileListDisplayMode(int(p_layout->get_value(p_section, "dock_filesystem_file_list_display_mode"))); + FileSystemDock::get_singleton()->set_file_list_display_mode(dock_filesystem_file_list_display_mode); + } + + if (p_layout->has_section_key(p_section, "dock_filesystem_selected_paths")) { + PackedStringArray dock_filesystem_selected_paths = p_layout->get_value(p_section, "dock_filesystem_selected_paths"); + for (int i = 0; i < dock_filesystem_selected_paths.size(); i++) { + FileSystemDock::get_singleton()->select_file(dock_filesystem_selected_paths[i]); + } + } + + // Restore collapsed state of FileSystemDock. + if (p_layout->has_section_key(p_section, "dock_filesystem_uncollapsed_paths")) { + PackedStringArray uncollapsed_tis = p_layout->get_value(p_section, "dock_filesystem_uncollapsed_paths"); + for (int i = 0; i < uncollapsed_tis.size(); i++) { + TreeItem *uncollapsed_ti = FileSystemDock::get_singleton()->get_tree_control()->get_item_with_metadata(uncollapsed_tis[i], 0); + if (uncollapsed_ti) { + uncollapsed_ti->set_collapsed(false); + } + } + FileSystemDock::get_singleton()->get_tree_control()->queue_redraw(); + } } -void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout, const String &p_section) { +void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) { + // Bottom panel. + + int center_split_offset = center_split->get_split_offset(); + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset", center_split_offset); + + int selected_bottom_panel_item_idx = -1; + for (int i = 0; i < bottom_panel_items.size(); i++) { + if (bottom_panel_items[i].button->is_pressed()) { + selected_bottom_panel_item_idx = i; + break; + } + } + if (selected_bottom_panel_item_idx != -1) { + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item", selected_bottom_panel_item_idx); + } + + // Debugger tab. + + int selected_default_debugger_tab_idx = EditorDebuggerNode::get_singleton()->get_default_debugger()->get_current_debugger_tab(); + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx", selected_default_debugger_tab_idx); + + // Main editor (plugin). + + int selected_main_editor_idx = -1; + for (int i = 0; i < main_editor_buttons.size(); i++) { + if (main_editor_buttons[i]->is_pressed()) { + selected_main_editor_idx = i; + break; + } + } + if (selected_main_editor_idx != -1) { + p_config_file->set_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx", selected_main_editor_idx); + } +} + +void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file) { + // Bottom panel. + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "center_split_offset")) { + int center_split_offset = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "center_split_offset"); + center_split->set_split_offset(center_split_offset); + } + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item")) { + int selected_bottom_panel_item_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_bottom_panel_item"); + if (selected_bottom_panel_item_idx >= 0 && selected_bottom_panel_item_idx < bottom_panel_items.size()) { + _bottom_panel_switch(true, selected_bottom_panel_item_idx); + } + } + + // Debugger tab. + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx")) { + int selected_default_debugger_tab_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_default_debugger_tab_idx"); + EditorDebuggerNode::get_singleton()->get_default_debugger()->switch_to_debugger(selected_default_debugger_tab_idx); + } + + // Main editor (plugin). + + if (p_config_file->has_section_key(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx")) { + int selected_main_editor_idx = p_config_file->get_value(EDITOR_NODE_CONFIG_SECTION, "selected_main_editor_idx"); + if (selected_main_editor_idx >= 0 && selected_main_editor_idx < main_editor_buttons.size()) { + callable_mp(this, &EditorNode::editor_select).call_deferred(selected_main_editor_idx); + } + } +} + +void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) { if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) { return; } - if (!p_layout->has_section(p_section) || !p_layout->has_section_key(p_section, "open_scenes")) { + if (!p_layout->has_section(EDITOR_NODE_CONFIG_SECTION) || + !p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) { return; } restoring_scenes = true; - Array scenes = p_layout->get_value(p_section, "open_scenes"); + PackedStringArray scenes = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes"); for (int i = 0; i < scenes.size(); i++) { load_scene(scenes[i]); } - save_layout(); + + if (p_layout->has_section_key(EDITOR_NODE_CONFIG_SECTION, "current_scene")) { + String current_scene = p_layout->get_value(EDITOR_NODE_CONFIG_SECTION, "current_scene"); + int current_scene_idx = scenes.find(current_scene); + set_current_scene(current_scene_idx); + } + + save_editor_layout_delayed(); restoring_scenes = false; } @@ -5122,10 +5262,10 @@ bool EditorNode::has_scenes_in_session() { if (err != OK) { return false; } - if (!config->has_section("EditorNode") || !config->has_section_key("EditorNode", "open_scenes")) { + if (!config->has_section(EDITOR_NODE_CONFIG_SECTION) || !config->has_section_key(EDITOR_NODE_CONFIG_SECTION, "open_scenes")) { return false; } - Array scenes = config->get_value("EditorNode", "open_scenes"); + Array scenes = config->get_value(EDITOR_NODE_CONFIG_SECTION, "open_scenes"); return !scenes.is_empty(); } @@ -5251,7 +5391,7 @@ void EditorNode::_layout_menu_option(int p_id) { } break; case SETTINGS_LAYOUT_DEFAULT: { _load_docks_from_config(default_layout, "docks"); - _save_docks(); + _save_editor_layout(); } break; default: { Ref<ConfigFile> config; @@ -5262,7 +5402,7 @@ void EditorNode::_layout_menu_option(int p_id) { } _load_docks_from_config(config, editor_layouts->get_item_text(p_id)); - _save_docks(); + _save_editor_layout(); } } } @@ -5334,7 +5474,7 @@ void EditorNode::_scene_tab_closed(int p_tab, int p_option) { _discard_changes(); } - save_layout(); + save_editor_layout_delayed(); _update_scene_tabs(); } @@ -5835,7 +5975,7 @@ void EditorNode::reload_scene(const String &p_path) { if (current_tab == scene_idx) { editor_data.apply_changes_in_editors(); - _set_scene_metadata(p_path); + _save_editor_states(p_path); } // Reload scene. @@ -6918,11 +7058,11 @@ EditorNode::EditorNode() { dock_slot[i]->set_use_hidden_tabs_for_min_size(true); } - dock_drag_timer = memnew(Timer); - add_child(dock_drag_timer); - dock_drag_timer->set_wait_time(0.5); - dock_drag_timer->set_one_shot(true); - dock_drag_timer->connect("timeout", callable_mp(this, &EditorNode::_save_docks)); + editor_layout_save_delay_timer = memnew(Timer); + add_child(editor_layout_save_delay_timer); + editor_layout_save_delay_timer->set_wait_time(0.5); + editor_layout_save_delay_timer->set_one_shot(true); + editor_layout_save_delay_timer->connect("timeout", callable_mp(this, &EditorNode::_save_editor_layout)); top_split = memnew(VSplitContainer); center_split->add_child(top_split); @@ -7403,7 +7543,7 @@ EditorNode::EditorNode() { FileSystemDock *filesystem_dock = memnew(FileSystemDock); filesystem_dock->connect("inherit", callable_mp(this, &EditorNode::_inherit_request)); filesystem_dock->connect("instantiate", callable_mp(this, &EditorNode::_instantiate_request)); - filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_docks)); + filesystem_dock->connect("display_mode_changed", callable_mp(this, &EditorNode::_save_editor_layout)); get_project_settings()->connect_filesystem_dock_signals(filesystem_dock); history_dock = memnew(HistoryDock); diff --git a/editor/editor_node.h b/editor/editor_node.h index c6fe70fc8d..9917fa16bc 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -430,7 +430,7 @@ private: PopupPanel *dock_select_popup = nullptr; Rect2 dock_select_rect[DOCK_SLOT_MAX]; TabContainer *dock_slot[DOCK_SLOT_MAX]; - Timer *dock_drag_timer = nullptr; + Timer *editor_layout_save_delay_timer = nullptr; bool docks_visible = true; int dock_popup_selected_idx = -1; int dock_select_rect_over_idx = -1; @@ -559,8 +559,8 @@ private: void _node_renamed(); void _editor_select_next(); void _editor_select_prev(); - void _set_scene_metadata(const String &p_file, int p_idx = -1); - void _get_scene_metadata(const String &p_file); + void _save_editor_states(const String &p_file, int p_idx = -1); + void _load_editor_plugin_states_from_config(const Ref<ConfigFile> &p_config_file); void _update_title(); void _update_scene_tabs(); void _version_control_menu_option(int p_idx); @@ -648,16 +648,19 @@ private: int _get_current_main_editor(); - void _save_docks(); - void _load_docks(); + void _save_editor_layout(); + void _load_editor_layout(); void _save_docks_to_config(Ref<ConfigFile> p_layout, const String &p_section); void _restore_floating_dock(const Dictionary &p_dock_dump, Control *p_wrapper, int p_slot_index); void _load_docks_from_config(Ref<ConfigFile> p_layout, const String &p_section); void _update_dock_slots_visibility(bool p_keep_selected_tabs = false); void _dock_tab_changed(int p_tab); - void _save_open_scenes_to_config(Ref<ConfigFile> p_layout, const String &p_section); - void _load_open_scenes_from_config(Ref<ConfigFile> p_layout, const String &p_section); + void _save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file); + void _load_central_editor_layout_from_config(Ref<ConfigFile> p_config_file); + + void _save_open_scenes_to_config(Ref<ConfigFile> p_layout); + void _load_open_scenes_from_config(Ref<ConfigFile> p_layout); void _update_layouts_menu(); void _layout_menu_option(int p_id); @@ -884,7 +887,7 @@ public: bool is_scene_in_use(const String &p_path); - void save_layout(); + void save_editor_layout_delayed(); void save_default_environment(); void open_export_template_manager(); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 442524d579..9e22a0ead6 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -471,7 +471,7 @@ bool EditorPlugin::build() { } void EditorPlugin::queue_save_layout() { - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); } void EditorPlugin::make_bottom_panel_item_visible(Control *p_item) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 5de2a47e0e..0eb76c6011 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -714,6 +714,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("editors/animation/onion_layers_past_color", Color(1, 0, 0)); _initial_set("editors/animation/onion_layers_future_color", Color(0, 1, 0)); + // Shader editor + _initial_set("editors/shader_editor/behavior/files/restore_shaders_on_load", true); + // Visual editors EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/minimap_opacity", 0.85, "0.0,1.0,0.01") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "editors/visual_editors/lines_curvature", 0.5, "0.0,1.0,0.01") diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 169e78c0f5..dd80ff49ac 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -87,14 +87,14 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory subdirectory_item->set_selectable(0, true); String lpath = p_dir->get_path(); subdirectory_item->set_metadata(0, lpath); - if (!p_select_in_favorites && (path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && path.get_base_dir() == lpath))) { + if (!p_select_in_favorites && (current_path == lpath || ((display_mode == DISPLAY_MODE_SPLIT) && current_path.get_base_dir() == lpath))) { subdirectory_item->select(0); // Keep select an item when re-created a tree // To prevent crashing when nothing is selected. subdirectory_item->set_as_cursor(0); } - if (p_unfold_path && path.begins_with(lpath) && path != lpath) { + if (p_unfold_path && current_path.begins_with(lpath) && current_path != lpath) { subdirectory_item->set_collapsed(false); } else { subdirectory_item->set_collapsed(uncollapsed_paths.find(lpath) < 0); @@ -155,7 +155,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type)); String file_metadata = lpath.path_join(fi.name); file_item->set_metadata(0, file_metadata); - if (!p_select_in_favorites && path == file_metadata) { + if (!p_select_in_favorites && current_path == file_metadata) { file_item->select(0); file_item->set_as_cursor(0); } @@ -168,7 +168,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory EditorResourcePreview::get_singleton()->queue_resource_preview(file_metadata, this, "_tree_thumbnail_done", udata); } } else if (display_mode == DISPLAY_MODE_SPLIT) { - if (lpath.get_base_dir() == path.get_base_dir()) { + if (lpath.get_base_dir() == current_path.get_base_dir()) { subdirectory_item->select(0); subdirectory_item->set_as_cursor(0); } @@ -186,8 +186,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory return parent_should_expand; } -Vector<String> FileSystemDock::_compute_uncollapsed_paths() { - // Register currently collapsed paths. +Vector<String> FileSystemDock::get_uncollapsed_paths() const { Vector<String> uncollapsed_paths; TreeItem *root = tree->get_root(); if (root) { @@ -196,21 +195,24 @@ Vector<String> FileSystemDock::_compute_uncollapsed_paths() { uncollapsed_paths.push_back(favorites_item->get_metadata(0)); } - TreeItem *resTree = root->get_first_child()->get_next(); - if (resTree) { - Vector<TreeItem *> needs_check; - needs_check.push_back(resTree); + // BFS to find all uncollapsed paths of the resource directory. + TreeItem *res_subtree = root->get_first_child()->get_next(); + if (res_subtree) { + List<TreeItem *> queue; + queue.push_back(res_subtree); - while (needs_check.size()) { - if (!needs_check[0]->is_collapsed()) { - uncollapsed_paths.push_back(needs_check[0]->get_metadata(0)); - TreeItem *child = needs_check[0]->get_first_child(); - while (child) { - needs_check.push_back(child); - child = child->get_next(); + while (!queue.is_empty()) { + TreeItem *ti = queue.back()->get(); + queue.pop_back(); + if (!ti->is_collapsed() && ti->get_child_count() > 0) { + Variant path = ti->get_metadata(0); + if (path) { + uncollapsed_paths.push_back(path); } } - needs_check.remove_at(0); + for (int i = 0; i < ti->get_child_count(); i++) { + queue.push_back(ti->get_child(i)); + } } } } @@ -286,7 +288,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo ti->set_tooltip_text(0, favorite); ti->set_selectable(0, true); ti->set_metadata(0, favorite); - if (p_select_in_favorites && favorite == path) { + if (p_select_in_favorites && favorite == current_path) { ti->select(0); ti->set_as_cursor(0); } @@ -329,7 +331,7 @@ void FileSystemDock::_update_display_mode(bool p_force) { toolbar2_hbc->hide(); } - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); file_list_vb->hide(); break; @@ -338,7 +340,7 @@ void FileSystemDock::_update_display_mode(bool p_force) { tree->set_v_size_flags(SIZE_EXPAND_FILL); tree->ensure_cursor_is_visible(); toolbar2_hbc->hide(); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); file_list_vb->show(); _update_file_list(true); @@ -388,7 +390,7 @@ void FileSystemDock::_notification(int p_what) { file_list_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_file_list_rmb_option)); tree_popup->connect("id_pressed", callable_mp(this, &FileSystemDock::_tree_rmb_option)); - current_path->connect("text_submitted", callable_mp(this, &FileSystemDock::_navigate_to_path).bind(false)); + current_path_line_edit->connect("text_submitted", callable_mp(this, &FileSystemDock::_navigate_to_path).bind(false)); always_show_folders = bool(EDITOR_GET("docks/filesystem/always_show_folders")); @@ -502,14 +504,14 @@ void FileSystemDock::_tree_multi_selected(Object *p_item, int p_column, bool p_s TreeItem *favorites_item = tree->get_root()->get_first_child(); if (selected->get_parent() == favorites_item && !String(selected->get_metadata(0)).ends_with("/")) { // Go to the favorites if we click in the favorites and the path has changed. - path = "Favorites"; + current_path = "Favorites"; } else { - path = selected->get_metadata(0); + current_path = selected->get_metadata(0); // Note: the "Favorites" item also leads to this path. } - // Set the current path. - _set_current_path_text(path); + // Display the current path. + _set_current_path_line_edit_text(current_path); _push_to_history(); // Update the file list. @@ -523,28 +525,28 @@ Vector<String> FileSystemDock::get_selected_paths() const { } String FileSystemDock::get_current_path() const { - return path; + return current_path; } String FileSystemDock::get_current_directory() const { - if (path.ends_with("/")) { - return path; + if (current_path.ends_with("/")) { + return current_path; } else { - return path.get_base_dir(); + return current_path.get_base_dir(); } } -void FileSystemDock::_set_current_path_text(const String &p_path) { +void FileSystemDock::_set_current_path_line_edit_text(const String &p_path) { if (p_path == "Favorites") { - current_path->set_text(TTR("Favorites")); + current_path_line_edit->set_text(TTR("Favorites")); } else { - current_path->set_text(path); + current_path_line_edit->set_text(current_path); } } void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_favorites) { if (p_path == "Favorites") { - path = p_path; + current_path = p_path; } else { String target_path = p_path; // If the path is a file, do not only go to the directory in the tree, also select the file in the file list. @@ -553,18 +555,18 @@ void FileSystemDock::_navigate_to_path(const String &p_path, bool p_select_in_fa } Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); if (da->file_exists(p_path)) { - path = target_path; + current_path = target_path; } else if (da->dir_exists(p_path)) { - path = target_path + "/"; + current_path = target_path + "/"; } else { ERR_FAIL_MSG(vformat("Cannot navigate to '%s' as it has not been found in the file system!", p_path)); } } - _set_current_path_text(path); + _set_current_path_line_edit_text(current_path); _push_to_history(); - _update_tree(_compute_uncollapsed_paths(), false, p_select_in_favorites, true); + _update_tree(get_uncollapsed_paths(), false, p_select_in_favorites, true); if (display_mode == DISPLAY_MODE_SPLIT) { _update_file_list(false); files->get_v_scroll_bar()->set_value(0); @@ -588,7 +590,7 @@ void FileSystemDock::navigate_to_path(const String &p_path) { } void FileSystemDock::_file_list_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, const Variant &p_udata) { - if ((file_list_vb->is_visible_in_tree() || path == p_path.get_base_dir()) && p_preview.is_valid()) { + if ((file_list_vb->is_visible_in_tree() || current_path == p_path.get_base_dir()) && p_preview.is_valid()) { Array uarr = p_udata; int idx = uarr[0]; String file = uarr[1]; @@ -749,9 +751,9 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { files->clear(); - _set_current_path_text(path); + _set_current_path_line_edit_text(current_path); - String directory = path; + String directory = current_path; String file = ""; int thumbnail_size = EDITOR_GET("docks/filesystem/thumbnail_size"); @@ -793,7 +795,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { // Build the FileInfo list. List<FileInfo> file_list; - if (path == "Favorites") { + if (current_path == "Favorites") { // Display the favorites. Vector<String> favorites_list = EditorSettings::get_singleton()->get_favorites(); for (const String &favorite : favorites_list) { @@ -842,8 +844,8 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) { } EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(directory); if (!efd) { - directory = path.get_base_dir(); - file = path.get_file(); + directory = current_path.get_base_dir(); + file = current_path.get_file(); efd = EditorFileSystem::get_singleton()->get_filesystem_path(directory); } if (!efd) { @@ -1084,7 +1086,7 @@ void FileSystemDock::_file_list_activate_file(int p_idx) { } void FileSystemDock::_preview_invalidated(const String &p_path) { - if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { + if (file_list_display_mode == FILE_LIST_DISPLAY_THUMBNAILS && p_path.get_base_dir() == current_path && searched_string.length() == 0 && file_list_vb->is_visible_in_tree()) { for (int i = 0; i < files->get_item_count(); i++) { if (files->get_item_metadata(i) == p_path) { // Re-request preview. @@ -1106,7 +1108,7 @@ void FileSystemDock::_fs_changed() { split_box->show(); if (tree->is_visible()) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } if (file_list_vb->is_visible()) { @@ -1146,11 +1148,11 @@ void FileSystemDock::_bw_history() { } void FileSystemDock::_update_history() { - path = history[history_pos]; - _set_current_path_text(path); + current_path = history[history_pos]; + _set_current_path_line_edit_text(current_path); if (tree->is_visible()) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); tree->grab_focus(); tree->ensure_cursor_is_visible(); } @@ -1164,9 +1166,9 @@ void FileSystemDock::_update_history() { } void FileSystemDock::_push_to_history() { - if (history[history_pos] != path) { + if (history[history_pos] != current_path) { history.resize(history_pos + 1); - history.push_back(path); + history.push_back(current_path); history_pos++; if (history.size() > history_max_size) { @@ -1255,7 +1257,7 @@ void FileSystemDock::_try_move_item(const FileOrFolder &p_item, const String &p_ for (int j = 0; j < ed->get_edited_scene_count(); j++) { if (ed->get_scene_path(j) == file_changed_paths[i]) { ed->get_edited_scene_root(j)->set_scene_file_path(new_item_path); - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); break; } } @@ -1292,7 +1294,7 @@ 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; } - const_cast<FileSystemDock *>(this)->path = new_path; + const_cast<FileSystemDock *>(this)->current_path = new_path; Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); @@ -1532,48 +1534,66 @@ void FileSystemDock::_file_removed(String p_file) { emit_signal(SNAME("file_removed"), p_file); // Find the closest parent directory available, in case multiple items were deleted along the same path. - path = p_file.get_base_dir(); + current_path = p_file.get_base_dir(); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - while (!da->dir_exists(path)) { - path = path.get_base_dir(); + while (!da->dir_exists(current_path)) { + current_path = current_path.get_base_dir(); } - current_path->set_text(path); + current_path_line_edit->set_text(current_path); } void FileSystemDock::_folder_removed(String p_folder) { emit_signal(SNAME("folder_removed"), p_folder); // Find the closest parent directory available, in case multiple items were deleted along the same path. - path = p_folder.get_base_dir(); + current_path = p_folder.get_base_dir(); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - while (!da->dir_exists(path)) { - path = path.get_base_dir(); + while (!da->dir_exists(current_path)) { + current_path = current_path.get_base_dir(); } - current_path->set_text(path); - EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(path); + current_path_line_edit->set_text(current_path); + EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->get_filesystem_path(current_path); if (efd) { efd->force_update(); } } void FileSystemDock::_rename_operation_confirm() { - String new_name = rename_dialog_text->get_text().strip_edges(); + if (!tree->is_anything_selected()) { + return; + } + TreeItem *s = tree->get_selected(); + int col_index = tree->get_selected_column(); + String new_name = s->get_text(col_index); + new_name = new_name.strip_edges(); + String old_name = to_rename.is_file ? to_rename.path.get_file() : to_rename.path.left(-1).get_file(); + + bool rename_error = false; if (new_name.length() == 0) { EditorNode::get_singleton()->show_warning(TTR("No name provided.")); - return; + rename_error = true; } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) { EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters.")); - return; + rename_error = true; + } else if (new_name.begins_with(".")) { + EditorNode::get_singleton()->show_warning(TTR("This filename begins with a dot rendering the file invisible to the editor.\nIf you want to rename it anyway, use your operating system's file manager.")); + rename_error = true; } else if (to_rename.is_file && to_rename.path.get_extension() != new_name.get_extension()) { if (!EditorFileSystem::get_singleton()->get_valid_extensions().find(new_name.get_extension())) { EditorNode::get_singleton()->show_warning(TTR("This file extension is not recognized by the editor.\nIf you want to rename it anyway, use your operating system's file manager.\nAfter renaming to an unknown extension, the file won't be shown in the editor anymore.")); - return; + rename_error = true; } } - String old_path = to_rename.path.ends_with("/") ? to_rename.path.substr(0, to_rename.path.length() - 1) : to_rename.path; + // Restores Tree to restore original names. + if (rename_error) { + s->set_text(col_index, old_name); + return; + } + + String old_path = to_rename.path.ends_with("/") ? to_rename.path.left(-1) : to_rename.path; String new_path = old_path.get_base_dir().path_join(new_name); if (old_path == new_path) { return; @@ -1592,6 +1612,7 @@ void FileSystemDock::_rename_operation_confirm() { if (da->file_exists(new_path) || da->dir_exists(new_path)) { #endif EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); + s->set_text(col_index, old_name); return; } @@ -1614,8 +1635,8 @@ void FileSystemDock::_rename_operation_confirm() { print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); - path = new_path; - current_path->set_text(path); + current_path = new_path; + current_path_line_edit->set_text(current_path); } void FileSystemDock::_duplicate_operation_confirm() { @@ -1758,8 +1779,8 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_cop print_verbose("FileSystem: saving moved scenes."); _save_scenes_after_move(file_renames); - path = p_to_path; - current_path->set_text(path); + current_path = p_to_path; + current_path_line_edit->set_text(current_path); } } } @@ -1840,8 +1861,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected switch (p_option) { case FILE_SHOW_IN_EXPLORER: { // Show the file/folder in the OS explorer. - String fpath = path; - if (path == "Favorites") { + String fpath = current_path; + if (current_path == "Favorites") { fpath = p_selected[0]; } @@ -1850,8 +1871,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_OPEN_EXTERNAL: { - String fpath = path; - if (path == "Favorites") { + String fpath = current_path; + if (current_path == "Favorites") { fpath = p_selected[0]; } @@ -1914,7 +1935,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected if (p_selected.size() == 1) { ProjectSettings::get_singleton()->set("application/run/main_scene", p_selected[0]); ProjectSettings::get_singleton()->save(); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); _update_file_list(true); } } break; @@ -1942,7 +1963,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } break; case FILE_REMOVE_FAVORITE: { @@ -1952,8 +1973,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected favorites_list.erase(p_selected[i]); } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); - if (path == "Favorites") { + _update_tree(get_uncollapsed_paths()); + if (current_path == "Favorites") { _update_file_list(true); } } break; @@ -1990,24 +2011,21 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_RENAME: { - // Rename the active file. - if (!p_selected.is_empty()) { + if (tree->is_anything_selected() && !p_selected.is_empty()) { + // Set to_rename variable for callback execution. to_rename.path = p_selected[0]; - if (to_rename.path != "res://") { - to_rename.is_file = !to_rename.path.ends_with("/"); - if (to_rename.is_file) { - String name = to_rename.path.get_file(); - rename_dialog->set_title(TTR("Renaming file:") + " " + name); - rename_dialog_text->set_text(name); - rename_dialog_text->select(0, name.rfind(".")); - } else { - String name = to_rename.path.substr(0, to_rename.path.length() - 1).get_file(); - rename_dialog->set_title(TTR("Renaming folder:") + " " + name); - rename_dialog_text->set_text(name); - rename_dialog_text->select(0, name.length()); - } - rename_dialog->popup_centered(Size2(250, 80) * EDSCALE); - rename_dialog_text->grab_focus(); + to_rename.is_file = !to_rename.path.ends_with("/"); + + // Edit node in Tree. + tree->grab_focus(); + tree->edit_selected(true); + + if (to_rename.is_file) { + String name = to_rename.path.get_file(); + tree->set_editor_selection(0, name.rfind(".")); + } else { + String name = to_rename.path.left(-1).get_file(); // Removes the "/" suffix for folders. + tree->set_editor_selection(0, name.length()); } } } break; @@ -2069,7 +2087,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_FOLDER: { - String directory = path; + String directory = current_path; if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } @@ -2078,7 +2096,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCENE: { - String directory = path; + String directory = current_path; if (!directory.ends_with("/")) { directory = directory.get_base_dir(); } @@ -2087,7 +2105,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } break; case FILE_NEW_SCRIPT: { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2116,7 +2134,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected new_resource_dialog->popup_create(true); } break; case FILE_NEW_TEXTFILE: { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2127,7 +2145,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected } void FileSystemDock::_resource_created() { - String fpath = path; + String fpath = current_path; if (!fpath.ends_with("/")) { fpath = fpath.get_base_dir(); } @@ -2168,7 +2186,7 @@ void FileSystemDock::_resource_created() { void FileSystemDock::_search_changed(const String &p_text, const Control *p_from) { if (searched_string.length() == 0) { // Register the uncollapsed paths before they change. - uncollapsed_paths_before_search = _compute_uncollapsed_paths(); + uncollapsed_paths_before_search = get_uncollapsed_paths(); } searched_string = p_text.to_lower(); @@ -2179,7 +2197,7 @@ void FileSystemDock::_search_changed(const String &p_text, const Control *p_from tree_search_box->set_text(searched_string); } - bool unfold_path = (p_text.is_empty() && !path.is_empty()); + bool unfold_path = (p_text.is_empty() && !current_path.is_empty()); switch (display_mode) { case DISPLAY_MODE_TREE_ONLY: { _update_tree(searched_string.length() == 0 ? uncollapsed_paths_before_search : Vector<String>(), false, false, unfold_path); @@ -2417,9 +2435,9 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } EditorSettings::get_singleton()->set_favorites(dirs); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); - if (display_mode == DISPLAY_MODE_SPLIT && path == "Favorites") { + if (display_mode == DISPLAY_MODE_SPLIT && current_path == "Favorites") { _update_file_list(true); } return; @@ -2467,7 +2485,7 @@ void FileSystemDock::drop_data_fw(const Point2 &p_point, const Variant &p_data, } } EditorSettings::get_singleton()->set_favorites(favorites_list); - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } } @@ -2491,7 +2509,7 @@ void FileSystemDock::_get_drag_target_folder(String &target, bool &target_favori } String ltarget = files->get_item_metadata(pos); - target = ltarget.ends_with("/") ? ltarget : path.get_base_dir(); + target = ltarget.ends_with("/") ? ltarget : current_path.get_base_dir(); return; } @@ -2674,7 +2692,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } #endif - path = fpath; + current_path = fpath; } } @@ -2702,7 +2720,7 @@ void FileSystemDock::_tree_empty_click(const Vector2 &p_pos, MouseButton p_butto return; } // Right click is pressed in the empty space of the tree. - path = "res://"; + current_path = "res://"; tree_popup->clear(); tree_popup->reset_size(); tree_popup->add_icon_item(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons")), TTR("New Folder..."), FILE_NEW_FOLDER); @@ -2763,7 +2781,7 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton return; } - path = current_path->get_text(); + current_path = current_path_line_edit->get_text(); file_list_popup->clear(); file_list_popup->reset_size(); @@ -2791,9 +2809,9 @@ void FileSystemDock::_file_multi_selected(int p_index, bool p_selected) { if (current == p_index) { String fpath = files->get_item_metadata(current); if (!fpath.ends_with("/")) { - path = fpath; + current_path = fpath; if (display_mode == DISPLAY_MODE_SPLIT) { - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); } } } @@ -3076,7 +3094,7 @@ void FileSystemDock::set_file_sort(FileSortOption p_file_sort) { file_sort = p_file_sort; // Update everything needed. - _update_tree(_compute_uncollapsed_paths()); + _update_tree(get_uncollapsed_paths()); _update_file_list(true); } @@ -3127,7 +3145,7 @@ void FileSystemDock::_bind_methods() { FileSystemDock::FileSystemDock() { singleton = this; set_name("FileSystem"); - path = "res://"; + current_path = "res://"; // `KeyModifierMask::CMD_OR_CTRL | Key::C` conflicts with other editor shortcuts. ED_SHORTCUT("filesystem_dock/copy_path", TTR("Copy Path"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::C); @@ -3163,11 +3181,11 @@ FileSystemDock::FileSystemDock() { button_hist_next->set_tooltip_text(TTR("Go to next selected folder/file.")); toolbar_hbc->add_child(button_hist_next); - current_path = memnew(LineEdit); - current_path->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); - current_path->set_h_size_flags(SIZE_EXPAND_FILL); - _set_current_path_text(path); - toolbar_hbc->add_child(current_path); + current_path_line_edit = memnew(LineEdit); + current_path_line_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); + current_path_line_edit->set_h_size_flags(SIZE_EXPAND_FILL); + _set_current_path_line_edit_text(current_path); + toolbar_hbc->add_child(current_path_line_edit); button_reload = memnew(Button); button_reload->connect("pressed", callable_mp(this, &FileSystemDock::_rescan)); @@ -3226,6 +3244,7 @@ FileSystemDock::FileSystemDock() { tree->connect("nothing_selected", callable_mp(this, &FileSystemDock::_tree_empty_selected)); tree->connect("gui_input", callable_mp(this, &FileSystemDock::_tree_gui_input)); tree->connect("mouse_exited", callable_mp(this, &FileSystemDock::_tree_mouse_exited)); + tree->connect("item_edited", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); file_list_vb = memnew(VBoxContainer); file_list_vb->set_v_size_flags(SIZE_EXPAND_FILL); @@ -3287,17 +3306,6 @@ FileSystemDock::FileSystemDock() { add_child(move_dialog); move_dialog->connect("dir_selected", callable_mp(this, &FileSystemDock::_move_dialog_confirm)); - rename_dialog = memnew(ConfirmationDialog); - VBoxContainer *rename_dialog_vb = memnew(VBoxContainer); - rename_dialog->add_child(rename_dialog_vb); - - rename_dialog_text = memnew(LineEdit); - rename_dialog_vb->add_margin_child(TTR("Name:"), rename_dialog_text); - rename_dialog->set_ok_button_text(TTR("Rename")); - add_child(rename_dialog); - rename_dialog->register_text_enter(rename_dialog_text); - rename_dialog->connect("confirmed", callable_mp(this, &FileSystemDock::_rename_operation_confirm)); - overwrite_dialog = memnew(ConfirmationDialog); add_child(overwrite_dialog); overwrite_dialog->set_ok_button_text(TTR("Overwrite")); diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h index e47178d294..05a6372830 100644 --- a/editor/filesystem_dock.h +++ b/editor/filesystem_dock.h @@ -125,7 +125,7 @@ private: Button *button_file_list_display_mode = nullptr; Button *button_hist_next = nullptr; Button *button_hist_prev = nullptr; - LineEdit *current_path = nullptr; + LineEdit *current_path_line_edit = nullptr; HBoxContainer *toolbar2_hbc = nullptr; LineEdit *tree_search_box = nullptr; @@ -152,8 +152,6 @@ private: DependencyRemoveDialog *remove_dialog = nullptr; EditorDirDialog *move_dialog = nullptr; - ConfirmationDialog *rename_dialog = nullptr; - LineEdit *rename_dialog_text = nullptr; ConfirmationDialog *duplicate_dialog = nullptr; LineEdit *duplicate_dialog_text = nullptr; DirectoryCreateDialog *make_dir_dialog = nullptr; @@ -185,7 +183,7 @@ private: int history_pos; int history_max_size; - String path; + String current_path; bool initialized = false; @@ -204,7 +202,6 @@ private: Ref<Texture2D> _get_tree_item_icon(bool p_is_valid, String p_file_type); bool _create_tree(TreeItem *p_parent, EditorFileSystemDirectory *p_dir, Vector<String> &uncollapsed_paths, bool p_select_in_favorites, bool p_unfold_path = false); - Vector<String> _compute_uncollapsed_paths(); void _update_tree(const Vector<String> &p_uncollapsed_paths = Vector<String>(), bool p_uncollapse_root = false, bool p_select_in_favorites = false, bool p_unfold_path = false); void _navigate_to_path(const String &p_path, bool p_select_in_favorites = false); @@ -295,7 +292,7 @@ private: void _search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items); - void _set_current_path_text(const String &p_path); + void _set_current_path_line_edit_text(const String &p_path); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; @@ -327,6 +324,7 @@ protected: public: Vector<String> get_selected_paths() const; + Vector<String> get_uncollapsed_paths() const; String get_current_path() const; String get_current_directory() const; @@ -351,6 +349,8 @@ public: void set_file_list_display_mode(FileListDisplayMode p_mode); FileListDisplayMode get_file_list_display_mode() { return file_list_display_mode; }; + Tree *get_tree_control() { return tree; } + FileSystemDock(); ~FileSystemDock(); }; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index f9ab37dce2..05a024f913 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -3981,10 +3981,6 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { Array selection = editor_selection->get_selected_nodes(); if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) { _reset_drag(); - - // Clear the selection - editor_selection->clear(); //_clear_canvas_items(); - editor_selection->add_node(p_canvas_item); } } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index a726ab09d5..1e577b7737 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -3299,6 +3299,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { orthogonal = false; auto_orthogonal = false; call_deferred(SNAME("update_transform_gizmo_view")); + _update_camera(0); _update_name(); } break; @@ -3308,8 +3309,8 @@ void Node3DEditorViewport::_menu_option(int p_option) { orthogonal = true; auto_orthogonal = false; call_deferred(SNAME("update_transform_gizmo_view")); + _update_camera(0); _update_name(); - } break; case VIEW_SWITCH_PERSPECTIVE_ORTHOGONAL: { _menu_option(orthogonal ? VIEW_PERSPECTIVE : VIEW_ORTHOGONAL); @@ -3402,7 +3403,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_NORMAL: case VIEW_DISPLAY_WIREFRAME: case VIEW_DISPLAY_OVERDRAW: - case VIEW_DISPLAY_SHADELESS: + case VIEW_DISPLAY_UNSHADED: case VIEW_DISPLAY_LIGHTING: case VIEW_DISPLAY_NORMAL_BUFFER: case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS: @@ -3429,7 +3430,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_OVERDRAW, - VIEW_DISPLAY_SHADELESS, + VIEW_DISPLAY_UNSHADED, VIEW_DISPLAY_LIGHTING, VIEW_DISPLAY_NORMAL_BUFFER, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, @@ -3778,15 +3779,9 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { if (p_state.has("distance")) { cursor.distance = p_state["distance"]; } - - if (p_state.has("use_orthogonal")) { - bool orth = p_state["use_orthogonal"]; - - if (orth) { - _menu_option(VIEW_ORTHOGONAL); - } else { - _menu_option(VIEW_PERSPECTIVE); - } + if (p_state.has("orthogonal")) { + bool orth = p_state["orthogonal"]; + _menu_option(orth ? VIEW_ORTHOGONAL : VIEW_PERSPECTIVE); } if (p_state.has("view_type")) { view_type = ViewType(p_state["view_type"].operator int()); @@ -3804,8 +3799,13 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { int display = p_state["display_mode"]; int idx = view_menu->get_popup()->get_item_index(display); - if (!view_menu->get_popup()->is_item_checked(idx)) { + if (idx != -1 && !view_menu->get_popup()->is_item_checked(idx)) { _menu_option(display); + } else { + idx = display_submenu->get_item_index(display); + if (idx != -1 && !display_submenu->is_item_checked(idx)) { + _menu_option(display); + } } } if (p_state.has("lock_rotation")) { @@ -3864,6 +3864,7 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { int idx = view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION); view_menu->get_popup()->set_item_checked(idx, half_res); + _update_shrink(); } if (p_state.has("cinematic_preview")) { previewing_cinema = p_state["cinematic_preview"]; @@ -3896,19 +3897,27 @@ Dictionary Node3DEditorViewport::get_state() const { d["y_rotation"] = cursor.y_rot; d["distance"] = cursor.distance; d["use_environment"] = camera->get_environment().is_valid(); - d["use_orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; + d["orthogonal"] = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; d["view_type"] = view_type; d["auto_orthogonal"] = auto_orthogonal; d["auto_orthogonal_enabled"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUTO_ORTHOGONAL)); - if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL))) { - d["display_mode"] = VIEW_DISPLAY_NORMAL; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME))) { - d["display_mode"] = VIEW_DISPLAY_WIREFRAME; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW))) { - d["display_mode"] = VIEW_DISPLAY_OVERDRAW; - } else if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS))) { - d["display_mode"] = VIEW_DISPLAY_SHADELESS; + + // Find selected display mode. + int display_mode = VIEW_DISPLAY_NORMAL; + for (int i = VIEW_DISPLAY_NORMAL; i < VIEW_DISPLAY_ADVANCED; i++) { + if (view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(i))) { + display_mode = i; + break; + } } + for (int i = VIEW_DISPLAY_ADVANCED + 1; i < VIEW_DISPLAY_MAX; i++) { + if (display_submenu->is_item_checked(display_submenu->get_item_index(i))) { + display_mode = i; + break; + } + } + d["display_mode"] = display_mode; + d["listener"] = viewport->is_audio_listener_3d(); d["doppler"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_AUDIO_DOPPLER)); d["gizmos"] = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_GIZMOS)); @@ -5005,7 +5014,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_wireframe", TTR("Display Wireframe")), VIEW_DISPLAY_WIREFRAME); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_overdraw", TTR("Display Overdraw")), VIEW_DISPLAY_OVERDRAW); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTR("Display Lighting")), VIEW_DISPLAY_LIGHTING); - view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); + view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_UNSHADED); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true); display_submenu->set_hide_on_checkable_item_selection(false); display_submenu->add_radio_check_item(TTR("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS); @@ -5074,7 +5083,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p const int normal_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL); const int wireframe_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME); const int overdraw_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW); - const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS); + const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_UNSHADED); const String unsupported_tooltip = TTR("Not available when using the OpenGL renderer."); view_menu->get_popup()->set_item_disabled(normal_idx, true); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index c4e1070d84..24a70680b7 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -125,25 +125,28 @@ class Node3DEditorViewport : public Control { VIEW_GIZMOS, VIEW_INFORMATION, VIEW_FRAME_TIME, + + // < Keep in sync with menu. VIEW_DISPLAY_NORMAL, VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_OVERDRAW, - VIEW_DISPLAY_SHADELESS, VIEW_DISPLAY_LIGHTING, + VIEW_DISPLAY_UNSHADED, VIEW_DISPLAY_ADVANCED, + // Advanced menu: + VIEW_DISPLAY_DEBUG_PSSM_SPLITS, VIEW_DISPLAY_NORMAL_BUFFER, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, + VIEW_DISPLAY_DEBUG_DECAL_ATLAS, VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, + VIEW_DISPLAY_DEBUG_SDFGI, + VIEW_DISPLAY_DEBUG_SDFGI_PROBES, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, VIEW_DISPLAY_DEBUG_SSIL, - VIEW_DISPLAY_DEBUG_PSSM_SPLITS, - VIEW_DISPLAY_DEBUG_DECAL_ATLAS, - VIEW_DISPLAY_DEBUG_SDFGI, - VIEW_DISPLAY_DEBUG_SDFGI_PROBES, VIEW_DISPLAY_DEBUG_GI_BUFFER, VIEW_DISPLAY_DEBUG_DISABLE_LOD, VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS, @@ -152,6 +155,8 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES, VIEW_DISPLAY_DEBUG_OCCLUDERS, VIEW_DISPLAY_MOTION_VECTORS, + VIEW_DISPLAY_MAX, + // > Keep in sync with menu. VIEW_LOCK_ROTATION, VIEW_CINEMATIC_PREVIEW, diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index b716cee3ff..a8d3ab948f 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -96,8 +96,9 @@ void PhysicalBone3DEditorPlugin::make_visible(bool p_visible) { } void PhysicalBone3DEditorPlugin::edit(Object *p_node) { - selected = static_cast<PhysicalBone3D *>(p_node); // Trust it - ERR_FAIL_COND(!selected); - - physical_bone_editor.set_selected(selected); + PhysicalBone3D *bone = Object::cast_to<PhysicalBone3D>(p_node); + if (bone) { + selected = bone; + physical_bone_editor.set_selected(selected); + } } diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index ac9b2a692d..51550acb94 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -2325,7 +2325,6 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, if (tab_container->get_current_tab() != i) { _go_to_tab(i); - _update_script_names(); } if (is_visible_in_tree()) { se->ensure_focus(); @@ -2707,7 +2706,7 @@ void ScriptEditor::_save_layout() { return; } - EditorNode::get_singleton()->save_layout(); + EditorNode::get_singleton()->save_editor_layout_delayed(); } void ScriptEditor::_editor_settings_changed() { @@ -3251,12 +3250,24 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = false; _update_script_names(); + + if (p_layout->has_section_key("ScriptEditor", "selected_script")) { + String selected_script = p_layout->get_value("ScriptEditor", "selected_script"); + // If the selected script is not in the list of open scripts, select nothing. + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se && se->get_edited_resource()->get_path() == selected_script) { + _go_to_tab(i); + break; + } + } + } } void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { Array scripts; Array helps; - + String selected_script; for (int i = 0; i < tab_container->get_tab_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { @@ -3265,6 +3276,10 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { continue; } + if (tab_container->get_current_tab_control() == tab_container->get_tab_control(i)) { + selected_script = path; + } + _save_editor_state(se); scripts.push_back(path); } @@ -3277,6 +3292,7 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { } p_layout->set_value("ScriptEditor", "open_scripts", scripts); + p_layout->set_value("ScriptEditor", "selected_script", selected_script); p_layout->set_value("ScriptEditor", "open_help", helps); p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset()); p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset()); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index abbd61ad89..e7d2d7a11f 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -180,6 +180,24 @@ void ShaderEditorPlugin::make_visible(bool p_visible) { void ShaderEditorPlugin::selected_notify() { } +TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader == p_for_shader) { + return edited_shader.shader_editor; + } + } + return nullptr; +} + +VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shader> &p_for_shader) { + for (EditedShader &edited_shader : edited_shaders) { + if (edited_shader.shader == p_for_shader) { + return edited_shader.visual_shader_editor; + } + } + return nullptr; +} + void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { if (EDITOR_GET("interface/multi_window/restore_windows_on_load") && window_wrapper->is_window_available() && p_layout->has_section_key("ShaderEditor", "window_rect")) { window_wrapper->restore_window_from_saved_position( @@ -189,6 +207,38 @@ void ShaderEditorPlugin::set_window_layout(Ref<ConfigFile> p_layout) { } else { window_wrapper->set_window_enabled(false); } + + if (!bool(EDITOR_GET("editors/shader_editor/behavior/files/restore_shaders_on_load"))) { + return; + } + if (!p_layout->has_section("ShaderEditor")) { + return; + } + if (!p_layout->has_section_key("ShaderEditor", "open_shaders") || + !p_layout->has_section_key("ShaderEditor", "selected_shader")) { + return; + } + + Array shaders = p_layout->get_value("ShaderEditor", "open_shaders"); + int selected_shader_idx = 0; + String selected_shader = p_layout->get_value("ShaderEditor", "selected_shader"); + for (int i = 0; i < shaders.size(); i++) { + String path = shaders[i]; + Ref<Resource> res = ResourceLoader::load(path); + if (res.is_valid()) { + edit(res.ptr()); + } + if (selected_shader == path) { + selected_shader_idx = i; + } + } + + if (p_layout->has_section_key("ShaderEditor", "split_offset")) { + main_split->set_split_offset(p_layout->get_value("ShaderEditor", "split_offset")); + } + + _update_shader_list(); + _shader_selected(selected_shader_idx); } void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { @@ -209,24 +259,25 @@ void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->erase_section_key("ShaderEditor", "window_screen_rect"); } } -} -TextShaderEditor *ShaderEditorPlugin::get_shader_editor(const Ref<Shader> &p_for_shader) { - for (EditedShader &edited_shader : edited_shaders) { - if (edited_shader.shader == p_for_shader) { - return edited_shader.shader_editor; - } - } - return nullptr; -} + Array shaders; + String selected_shader; + for (int i = 0; i < shader_tabs->get_tab_count(); i++) { + EditedShader edited_shader = edited_shaders[i]; + if (edited_shader.shader_editor || edited_shader.visual_shader_editor) { + shaders.push_back(edited_shader.shader->get_path()); -VisualShaderEditor *ShaderEditorPlugin::get_visual_shader_editor(const Ref<Shader> &p_for_shader) { - for (EditedShader &edited_shader : edited_shaders) { - if (edited_shader.shader == p_for_shader) { - return edited_shader.visual_shader_editor; + TextShaderEditor *shader_editor = Object::cast_to<TextShaderEditor>(shader_tabs->get_current_tab_control()); + VisualShaderEditor *visual_shader_editor = Object::cast_to<VisualShaderEditor>(shader_tabs->get_current_tab_control()); + + if ((shader_editor && edited_shader.shader_editor == shader_editor) || (visual_shader_editor && edited_shader.visual_shader_editor == visual_shader_editor)) { + selected_shader = edited_shader.shader->get_path(); + } } } - return nullptr; + p_layout->set_value("ShaderEditor", "open_shaders", shaders); + p_layout->set_value("ShaderEditor", "split_offset", main_split->get_split_offset()); + p_layout->set_value("ShaderEditor", "selected_shader", selected_shader); } void ShaderEditorPlugin::save_external_data() { @@ -247,6 +298,10 @@ void ShaderEditorPlugin::apply_changes() { } void ShaderEditorPlugin::_shader_selected(int p_index) { + if (p_index >= (int)edited_shaders.size()) { + return; + } + if (edited_shaders[p_index].shader_editor) { edited_shaders[p_index].shader_editor->validate_script(); } diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index bc508976ca..45b48a2f91 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -108,12 +108,13 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; virtual void selected_notify() override; - virtual void set_window_layout(Ref<ConfigFile> p_layout) override; - virtual void get_window_layout(Ref<ConfigFile> p_layout) override; TextShaderEditor *get_shader_editor(const Ref<Shader> &p_for_shader); VisualShaderEditor *get_visual_shader_editor(const Ref<Shader> &p_for_shader); + virtual void set_window_layout(Ref<ConfigFile> p_layout) override; + virtual void get_window_layout(Ref<ConfigFile> p_layout) override; + virtual void save_external_data() override; virtual void apply_changes() override; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 85906ef9d3..0cfeacc9b1 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -832,8 +832,8 @@ void Skeleton3DEditor::create_editors() { void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + create_editors(); update_joint_tree(); - update_editors(); joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); @@ -946,8 +946,6 @@ void fragment() { handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); handles_mesh.instantiate(); handles_mesh_instance->set_mesh(handles_mesh); - - create_editors(); } void Skeleton3DEditor::update_bone_original() { diff --git a/main/main.cpp b/main/main.cpp index 797aa32441..86de6497d0 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3446,6 +3446,8 @@ void Main::cleanup(bool p_force) { movie_writer->end(); } + ResourceLoader::clear_thread_load_tasks(); + ResourceLoader::remove_custom_loaders(); ResourceSaver::remove_custom_savers(); @@ -3462,8 +3464,6 @@ void Main::cleanup(bool p_force) { ResourceLoader::clear_translation_remaps(); ResourceLoader::clear_path_remaps(); - ResourceLoader::clear_thread_load_tasks(); - ScriptServer::finish_languages(); // Sync pending commands that may have been queued from a different thread during ScriptServer finalization diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index f09acb11c6..b8fc8004c9 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -748,7 +748,7 @@ TreeItem *TreeItem::get_first_child() const { return first_child; } -TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { +TreeItem *TreeItem::_get_prev_in_tree(bool p_wrap, bool p_include_invisible) { TreeItem *current = this; TreeItem *prev_item = current->get_prev(); @@ -771,7 +771,7 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { } } else { current = prev_item; - while (!current->collapsed && current->first_child) { + while ((!current->collapsed || p_include_invisible) && current->first_child) { //go to the very end current = current->first_child; @@ -786,9 +786,9 @@ TreeItem *TreeItem::_get_prev_visible(bool p_wrap) { TreeItem *TreeItem::get_prev_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *prev_item = this->_get_prev_visible(p_wrap); + TreeItem *prev_item = this->_get_prev_in_tree(p_wrap); while (prev_item && !prev_item->is_visible()) { - prev_item = prev_item->_get_prev_visible(p_wrap); + prev_item = prev_item->_get_prev_in_tree(p_wrap); if (prev_item == loop) { // Check that we haven't looped all the way around to the start. prev_item = nullptr; @@ -798,10 +798,10 @@ TreeItem *TreeItem::get_prev_visible(bool p_wrap) { return prev_item; } -TreeItem *TreeItem::_get_next_visible(bool p_wrap) { +TreeItem *TreeItem::_get_next_in_tree(bool p_wrap, bool p_include_invisible) { TreeItem *current = this; - if (!current->collapsed && current->first_child) { + if ((!current->collapsed || p_include_invisible) && current->first_child) { current = current->first_child; } else if (current->next) { @@ -827,9 +827,9 @@ TreeItem *TreeItem::_get_next_visible(bool p_wrap) { TreeItem *TreeItem::get_next_visible(bool p_wrap) { TreeItem *loop = this; - TreeItem *next_item = this->_get_next_visible(p_wrap); + TreeItem *next_item = this->_get_next_in_tree(p_wrap); while (next_item && !next_item->is_visible()) { - next_item = next_item->_get_next_visible(p_wrap); + next_item = next_item->_get_next_in_tree(p_wrap); if (next_item == loop) { // Check that we haven't looped all the way around to the start. next_item = nullptr; @@ -839,6 +839,16 @@ TreeItem *TreeItem::get_next_visible(bool p_wrap) { return next_item; } +TreeItem *TreeItem::get_prev_in_tree(bool p_wrap) { + TreeItem *prev_item = this->_get_prev_in_tree(p_wrap, true); + return prev_item; +} + +TreeItem *TreeItem::get_next_in_tree(bool p_wrap) { + TreeItem *next_item = this->_get_next_in_tree(p_wrap, true); + return next_item; +} + TreeItem *TreeItem::get_child(int p_index) { _create_children_cache(); @@ -1539,6 +1549,9 @@ void TreeItem::_bind_methods() { ClassDB::bind_method(D_METHOD("get_parent"), &TreeItem::get_parent); ClassDB::bind_method(D_METHOD("get_first_child"), &TreeItem::get_first_child); + ClassDB::bind_method(D_METHOD("get_next_in_tree", "wrap"), &TreeItem::get_next_in_tree, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_prev_in_tree", "wrap"), &TreeItem::get_prev_in_tree, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_next_visible", "wrap"), &TreeItem::get_next_visible, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_prev_visible", "wrap"), &TreeItem::get_prev_visible, DEFVAL(false)); @@ -3851,14 +3864,14 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { } } -bool Tree::edit_selected() { +bool Tree::edit_selected(bool p_force_edit) { TreeItem *s = get_selected(); ERR_FAIL_COND_V_MSG(!s, false, "No item selected."); ensure_cursor_is_visible(); int col = get_selected_column(); ERR_FAIL_INDEX_V_MSG(col, columns.size(), false, "No item column selected."); - if (!s->cells[col].editable) { + if (!s->cells[col].editable && !p_force_edit) { return false; } @@ -3964,6 +3977,14 @@ bool Tree::is_editing() { return popup_editor->is_visible(); } +void Tree::set_editor_selection(int p_from_line, int p_to_line, int p_from_column, int p_to_column, int p_caret) { + if (p_from_column == -1 || p_to_column == -1) { + line_editor->select(p_from_line, p_to_line); + } else { + text_editor->select(p_from_line, p_from_column, p_to_line, p_to_column, p_caret); + } +} + Size2 Tree::get_internal_min_size() const { Size2i size; if (root) { @@ -4995,6 +5016,26 @@ TreeItem *Tree::get_item_with_text(const String &p_find) const { return nullptr; } +TreeItem *Tree::get_item_with_metadata(const Variant &p_find, int p_column) const { + if (p_column < 0) { + for (TreeItem *current = root; current; current = current->get_next_in_tree()) { + for (int i = 0; i < columns.size(); i++) { + if (current->get_metadata(i) == p_find) { + return current; + } + } + } + return nullptr; + } + + for (TreeItem *current = root; current; current = current->get_next_in_tree()) { + if (current->get_metadata(p_column) == p_find) { + return current; + } + } + return nullptr; +} + void Tree::_do_incr_search(const String &p_add) { uint64_t time = OS::get_singleton()->get_ticks_usec() / 1000; // convert to msec uint64_t diff = time - last_keypress; @@ -5360,7 +5401,7 @@ void Tree::_bind_methods() { ClassDB::bind_method(D_METHOD("get_edited"), &Tree::get_edited); ClassDB::bind_method(D_METHOD("get_edited_column"), &Tree::get_edited_column); - ClassDB::bind_method(D_METHOD("edit_selected"), &Tree::edit_selected); + ClassDB::bind_method(D_METHOD("edit_selected", "force_edit"), &Tree::edit_selected, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_custom_popup_rect"), &Tree::get_custom_popup_rect); ClassDB::bind_method(D_METHOD("get_item_area_rect", "item", "column", "button_index"), &Tree::get_item_rect, DEFVAL(-1), DEFVAL(-1)); ClassDB::bind_method(D_METHOD("get_item_at_position", "position"), &Tree::get_item_at_position); diff --git a/scene/gui/tree.h b/scene/gui/tree.h index 27d8949370..24b649b040 100644 --- a/scene/gui/tree.h +++ b/scene/gui/tree.h @@ -217,8 +217,8 @@ private: void _propagate_check_through_children(int p_column, bool p_checked, bool p_emit_signal); void _propagate_check_through_parents(int p_column, bool p_emit_signal); - TreeItem *_get_prev_visible(bool p_wrap = false); - TreeItem *_get_next_visible(bool p_wrap = false); + TreeItem *_get_prev_in_tree(bool p_wrap = false, bool p_include_invisible = false); + TreeItem *_get_next_in_tree(bool p_wrap = false, bool p_include_invisible = false); public: void set_text(int p_column, String p_text); @@ -344,6 +344,9 @@ public: TreeItem *get_parent() const; TreeItem *get_first_child() const; + TreeItem *get_prev_in_tree(bool p_wrap = false); + TreeItem *get_next_in_tree(bool p_wrap = false); + TreeItem *get_prev_visible(bool p_wrap = false); TreeItem *get_next_visible(bool p_wrap = false); @@ -724,13 +727,15 @@ public: int get_item_offset(TreeItem *p_item) const; Rect2 get_item_rect(TreeItem *p_item, int p_column = -1, int p_button = -1) const; - bool edit_selected(); + bool edit_selected(bool p_force_edit = false); bool is_editing(); + void set_editor_selection(int p_from_line, int p_to_line, int p_from_column = -1, int p_to_column = -1, int p_caret = 0); // First item that starts with the text, from the current focused item down and wraps around. TreeItem *search_item_text(const String &p_find, int *r_col = nullptr, bool p_selectable = false); // First item that matches the whole text, from the first item down. TreeItem *get_item_with_text(const String &p_find) const; + TreeItem *get_item_with_metadata(const Variant &p_find, int p_column = -1) const; Point2 get_scroll() const; void scroll_to_item(TreeItem *p_item, bool p_center_on_item = false); diff --git a/scene/resources/canvas_item_material.cpp b/scene/resources/canvas_item_material.cpp index 0d5d735ed0..31c8e68ea5 100644 --- a/scene/resources/canvas_item_material.cpp +++ b/scene/resources/canvas_item_material.cpp @@ -161,7 +161,7 @@ void CanvasItemMaterial::flush_changes() { void CanvasItemMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -287,8 +287,8 @@ CanvasItemMaterial::CanvasItemMaterial() : set_particles_anim_loop(false); current_key.invalid_key = 1; - is_initialized = true; - _queue_shader_change(); + + _mark_initialized(callable_mp(this, &CanvasItemMaterial::_queue_shader_change)); } CanvasItemMaterial::~CanvasItemMaterial() { diff --git a/scene/resources/canvas_item_material.h b/scene/resources/canvas_item_material.h index cf5df76147..7dddd74a31 100644 --- a/scene/resources/canvas_item_material.h +++ b/scene/resources/canvas_item_material.h @@ -105,7 +105,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; BlendMode blend_mode = BLEND_MODE_MIX; LightMode light_mode = LIGHT_MODE_NORMAL; bool particles_animation = false; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index d0aa224773..d35c49b266 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -82,6 +82,23 @@ void Material::_validate_property(PropertyInfo &p_property) const { } } +void Material::_mark_initialized(const Callable &p_queue_shader_change_callable) { + // If this is happening as part of resource loading, it is not safe to queue the update + // as an addition to the dirty list, unless the load is happening on the main thread. + if (ResourceLoader::is_within_load() && Thread::get_caller_id() != Thread::get_main_id()) { + DEV_ASSERT(init_state != INIT_STATE_READY); + if (init_state == INIT_STATE_UNINITIALIZED) { // Prevent queueing twice. + // Queue an individual update of this material (the ResourceLoader knows how to handle deferred calls safely). + p_queue_shader_change_callable.call_deferred(); + init_state = INIT_STATE_INITIALIZING; + } + } else { + // Straightforward conditions. + init_state = INIT_STATE_READY; + p_queue_shader_change_callable.callv(Array()); + } +} + void Material::inspect_native_shader_code() { SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop()); RID shader = get_shader_rid(); @@ -1485,7 +1502,7 @@ void BaseMaterial3D::flush_changes() { void BaseMaterial3D::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -3028,8 +3045,7 @@ BaseMaterial3D::BaseMaterial3D(bool p_orm) : flags[FLAG_ALBEDO_TEXTURE_MSDF] = false; flags[FLAG_USE_TEXTURE_REPEAT] = true; - is_initialized = true; - _queue_shader_change(); + _mark_initialized(callable_mp(this, &BaseMaterial3D::_queue_shader_change)); } BaseMaterial3D::~BaseMaterial3D() { diff --git a/scene/resources/material.h b/scene/resources/material.h index 1fa9a24bc5..b70522dda1 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -46,6 +46,12 @@ class Material : public Resource { Ref<Material> next_pass; int render_priority; + enum { + INIT_STATE_UNINITIALIZED, + INIT_STATE_INITIALIZING, + INIT_STATE_READY, + } init_state = INIT_STATE_UNINITIALIZED; + void inspect_native_shader_code(); protected: @@ -56,6 +62,9 @@ protected: void _validate_property(PropertyInfo &p_property) const; + void _mark_initialized(const Callable &p_queue_shader_change_callable); + bool _is_initialized() { return init_state == INIT_STATE_READY; } + GDVIRTUAL0RC(RID, _get_shader_rid) GDVIRTUAL0RC(Shader::Mode, _get_shader_mode) GDVIRTUAL0RC(bool, _can_do_next_pass) @@ -452,7 +461,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; bool orm; Color albedo; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index 41edbcb726..cb94d5af36 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -915,7 +915,7 @@ void ParticleProcessMaterial::flush_changes() { void ParticleProcessMaterial::_queue_shader_change() { MutexLock lock(material_mutex); - if (is_initialized && !element.in_list()) { + if (_is_initialized() && !element.in_list()) { dirty_materials->add(&element); } } @@ -1889,8 +1889,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : current_key.invalid_key = 1; - is_initialized = true; - _queue_shader_change(); + _mark_initialized(callable_mp(this, &ParticleProcessMaterial::_queue_shader_change)); } ParticleProcessMaterial::~ParticleProcessMaterial() { diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index c32a143cdc..533bd9636f 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -261,7 +261,6 @@ private: _FORCE_INLINE_ void _queue_shader_change(); _FORCE_INLINE_ bool _is_shader_dirty() const; - bool is_initialized = false; Vector3 direction; float spread = 0.0f; float flatness = 0.0f; diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index c30e009356..4807af3c27 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -150,32 +150,31 @@ Error ResourceLoaderText::_parse_ext_resource(VariantParser::Stream *p_stream, R String path = ext_resources[id].path; String type = ext_resources[id].type; + Ref<ResourceLoader::LoadToken> &load_token = ext_resources[id].load_token; - if (ext_resources[id].cache.is_valid()) { - r_res = ext_resources[id].cache; - } else if (use_sub_threads) { - Ref<Resource> res = ResourceLoader::load_threaded_get(path); + if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies. + Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err); if (res.is_null()) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_MISSING_DEPENDENCIES; - error_text = "[ext_resource] referenced nonexistent resource at: " + path; - _printerr(); - err = error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); + if (!ResourceLoader::is_cleaning_tasks()) { + if (ResourceLoader::get_abort_on_missing_resources()) { + error = ERR_FILE_MISSING_DEPENDENCIES; + error_text = "[ext_resource] referenced non-existent resource at: " + path; + _printerr(); + err = error; + } else { + ResourceLoader::notify_dependency_error(local_path, path, type); + } } } else { - ext_resources[id].cache = res; +#ifdef TOOLS_ENABLED + //remember ID for saving + res->set_id_for_path(path, id); +#endif r_res = res; } } else { - error = ERR_FILE_MISSING_DEPENDENCIES; - error_text = "[ext_resource] referenced non-loaded resource at: " + path; - _printerr(); - err = error; + r_res = Ref<Resource>(); } - } else { - r_res = Ref<Resource>(); } VariantParser::get_token(p_stream, token, line, r_err_str); @@ -462,48 +461,20 @@ Error ResourceLoaderText::load() { path = remaps[path]; } - ExtResource er; - er.path = path; - er.type = type; - - if (use_sub_threads) { - Error err = ResourceLoader::load_threaded_request(path, type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path); - - if (err != OK) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_CORRUPT; - error_text = "[ext_resource] referenced broken resource at: " + path; - _printerr(); - return error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); - } - } - - } else { - Ref<Resource> res = ResourceLoader::load(path, type); - - if (res.is_null()) { - if (ResourceLoader::get_abort_on_missing_resources()) { - error = ERR_FILE_CORRUPT; - error_text = "[ext_resource] referenced nonexistent resource at: " + path; - _printerr(); - return error; - } else { - ResourceLoader::notify_dependency_error(local_path, path, type); - } + ext_resources[id].path = path; + ext_resources[id].type = type; + ext_resources[id].load_token = ResourceLoader::_load_start(path, type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE); + if (!ext_resources[id].load_token.is_valid()) { + if (ResourceLoader::get_abort_on_missing_resources()) { + error = ERR_FILE_CORRUPT; + error_text = "[ext_resource] referenced non-existent resource at: " + path; + _printerr(); + return error; } else { -#ifdef TOOLS_ENABLED - //remember ID for saving - res->set_id_for_path(local_path, id); -#endif + ResourceLoader::notify_dependency_error(local_path, path, type); } - - er.cache = res; } - ext_resources[id] = er; - error = VariantParser::parse_tag(&stream, lines, error_text, next_tag, &rp); if (error) { diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index 25001d8023..c35a594f72 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -48,7 +48,7 @@ class ResourceLoaderText { VariantParser::StreamFile stream; struct ExtResource { - Ref<Resource> cache; + Ref<ResourceLoader::LoadToken> load_token; String path; String type; }; |