diff options
28 files changed, 720 insertions, 387 deletions
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index ed5e482296..58ad61b621 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -40,6 +40,7 @@ #include "core/string/print_string.h" #include "core/string/translation.h" #include "core/variant/variant_parser.h" +#include "servers/rendering_server.h" #ifdef DEBUG_LOAD_THREADED #define print_lt(m_text) print_line(m_text) @@ -585,6 +586,16 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const *r_progress = _dependency_get_progress(local_path); } + // Support userland polling in a loop on the main thread. + if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { + uint64_t frame = Engine::get_singleton()->get_process_frames(); + if (frame == load_task.last_progress_check_main_thread_frame) { + _ensure_load_progress(); + } else { + load_task.last_progress_check_main_thread_frame = frame; + } + } + return status; } @@ -613,6 +624,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } return Ref<Resource>(); } + + // Support userland requesting on the main thread before the load is reported to be complete. + if (Thread::is_main_thread() && !load_token->local_path.is_empty()) { + const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path]; + while (load_task.status == THREAD_LOAD_IN_PROGRESS) { + if (!_ensure_load_progress()) { + // This local poll loop is not needed. + break; + } + thread_load_lock.~MutexLock(); + OS::get_singleton()->delay_usec(1000); + new (&thread_load_lock) MutexLock(thread_load_mutex); + } + } + res = _load_complete_inner(*load_token, r_error, thread_load_lock); if (load_token->unreference()) { memdelete(load_token); @@ -731,6 +757,17 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } } +bool ResourceLoader::_ensure_load_progress() { + // Some servers may need a new engine iteration to allow the load to progress. + // Since the only known one is the rendering server (in single thread mode), let's keep it simple and just sync it. + // This may be refactored in the future to support other servers and have less coupling. + if (OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD) { + return false; // Not needed. + } + RenderingServer::get_singleton()->sync(); + return true; +} + Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) { ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0. const String &local_path = _validate_local_path(p_path); diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index c48f39b5cc..46df79ea22 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -174,6 +174,7 @@ private: String type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; + uint64_t last_progress_check_main_thread_frame = UINT64_MAX; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; Error error = OK; @@ -197,6 +198,8 @@ private: static float _dependency_get_progress(const String &p_path); + static bool _ensure_load_progress(); + 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); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index a7c0a0353e..8f56ca37de 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -33,7 +33,6 @@ #include "core/object/script_language.h" #include "core/os/os.h" #include "core/os/thread_safe.h" -#include "core/templates/command_queue_mt.h" WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1; @@ -46,7 +45,9 @@ void WorkerThreadPool::Task::free_template_userdata() { WorkerThreadPool *WorkerThreadPool::singleton = nullptr; -thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr; +#ifdef THREADS_ENABLED +thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {}; +#endif void WorkerThreadPool::_process_task(Task *p_task) { #ifdef THREADS_ENABLED @@ -419,6 +420,34 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { return OK; } +void WorkerThreadPool::_lock_unlockable_mutexes() { +#ifdef THREADS_ENABLED + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlockable_mutexes[i]) { + if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { + ((Mutex *)unlockable_mutexes[i])->lock(); + } else { + ((BinaryMutex *)unlockable_mutexes[i])->lock(); + } + } + } +#endif +} + +void WorkerThreadPool::_unlock_unlockable_mutexes() { +#ifdef THREADS_ENABLED + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlockable_mutexes[i]) { + if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { + ((Mutex *)unlockable_mutexes[i])->unlock(); + } else { + ((BinaryMutex *)unlockable_mutexes[i])->unlock(); + } + } + } +#endif +} + void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) { // Keep processing tasks until the condition to stop waiting is met. @@ -426,6 +455,7 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T while (true) { Task *task_to_process = nullptr; + bool relock_unlockables = false; { MutexLock lock(task_mutex); bool was_signaled = p_caller_pool_thread->signaled; @@ -463,13 +493,9 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T if (!task_to_process) { p_caller_pool_thread->awaited_task = p_task; - if (flushing_cmd_queue) { - flushing_cmd_queue->unlock(); - } + _unlock_unlockable_mutexes(); + relock_unlockables = true; p_caller_pool_thread->cond_var.wait(lock); - if (flushing_cmd_queue) { - flushing_cmd_queue->lock(); - } DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER); p_caller_pool_thread->awaited_task = nullptr; @@ -477,6 +503,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T } } + if (relock_unlockables) { + _lock_unlockable_mutexes(); + } + if (task_to_process) { _process_task(task_to_process); } @@ -603,13 +633,9 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { { Group *group = *groupp; - if (flushing_cmd_queue) { - flushing_cmd_queue->unlock(); - } + _unlock_unlockable_mutexes(); group->done_semaphore.wait(); - if (flushing_cmd_queue) { - flushing_cmd_queue->lock(); - } + _lock_unlockable_mutexes(); uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment. uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later. @@ -633,16 +659,41 @@ int WorkerThreadPool::get_thread_index() { return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1; } -void WorkerThreadPool::thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue) { - ERR_FAIL_COND(flushing_cmd_queue != nullptr); - flushing_cmd_queue = p_queue; +#ifdef THREADS_ENABLED +uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) { + return _thread_enter_unlock_allowance_zone(p_mutex, false); +} + +uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) { + return _thread_enter_unlock_allowance_zone(p_mutex, true); } -void WorkerThreadPool::thread_exit_command_queue_mt_flush() { - ERR_FAIL_NULL(flushing_cmd_queue); - flushing_cmd_queue = nullptr; +uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) { + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlikely(unlockable_mutexes[i] == (uintptr_t)p_mutex)) { + // Already registered in the current thread. + return UINT32_MAX; + } + if (!unlockable_mutexes[i]) { + unlockable_mutexes[i] = (uintptr_t)p_mutex; + if (p_is_binary) { + unlockable_mutexes[i] |= 1; + } + return i; + } + } + ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug."); } +void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) { + if (p_zone_id == UINT32_MAX) { + return; + } + DEV_ASSERT(unlockable_mutexes[p_zone_id]); + unlockable_mutexes[p_zone_id] = 0; +} +#endif + void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) { ERR_FAIL_COND(threads.size() > 0); if (p_thread_count < 0) { diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index a9cf260a0f..8774143abf 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -41,8 +41,6 @@ #include "core/templates/rid.h" #include "core/templates/safe_refcount.h" -class CommandQueueMT; - class WorkerThreadPool : public Object { GDCLASS(WorkerThreadPool, Object) public: @@ -163,7 +161,10 @@ private: static WorkerThreadPool *singleton; - static thread_local CommandQueueMT *flushing_cmd_queue; +#ifdef THREADS_ENABLED + static const uint32_t MAX_UNLOCKABLE_MUTEXES = 2; + static thread_local uintptr_t unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES]; +#endif TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description); GroupID _add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description); @@ -190,6 +191,13 @@ private: void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task); +#ifdef THREADS_ENABLED + static uint32_t _thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary); +#endif + + void _lock_unlockable_mutexes(); + void _unlock_unlockable_mutexes(); + protected: static void _bind_methods(); @@ -232,8 +240,14 @@ public: static WorkerThreadPool *get_singleton() { return singleton; } static int get_thread_index(); - static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue); - static void thread_exit_command_queue_mt_flush(); +#ifdef THREADS_ENABLED + static uint32_t thread_enter_unlock_allowance_zone(Mutex *p_mutex); + static uint32_t thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex); + static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id); +#else + static uint32_t thread_enter_unlock_allowance_zone(void *p_mutex) { return UINT32_MAX; } + static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {} +#endif void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3); void finish(); diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index 349404d75b..0748e9cb83 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -364,7 +364,7 @@ class CommandQueueMT { lock(); - WorkerThreadPool::thread_enter_command_queue_mt_flush(this); + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&mutex); while (flush_read_ptr < command_mem.size()) { uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr]; flush_read_ptr += 8; @@ -383,7 +383,7 @@ class CommandQueueMT { flush_read_ptr += size; } - WorkerThreadPool::thread_exit_command_queue_mt_flush(); + WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); command_mem.clear(); flush_read_ptr = 0; diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml index 1961ca2b0e..cb0db46595 100644 --- a/doc/classes/ResourceLoader.xml +++ b/doc/classes/ResourceLoader.xml @@ -87,7 +87,7 @@ <param index="0" name="path" type="String" /> <description> Returns the resource loaded by [method load_threaded_request]. - If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading. + If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading. However, it's recommended to use [method load_threaded_get_status] to known when the load has actually completed. </description> </method> <method name="load_threaded_get_status"> @@ -97,6 +97,7 @@ <description> Returns the status of a threaded loading operation started with [method load_threaded_request] for the resource at [param path]. See [enum ThreadLoadStatus] for possible return values. An array variable can optionally be passed via [param progress], and will return a one-element array containing the percentage of completion of the threaded loading. + [b]Note:[/b] The recommended way of using this method is to call it during different frames (e.g., in [method Node._process], instead of a loop). </description> </method> <method name="load_threaded_request"> diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 8628fd9e11..a3d0dfb89b 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -5084,17 +5084,7 @@ void AnimationTrackEditor::_fetch_value_track_options(const NodePath &p_path, An PropertyInfo h = _find_hint_for_track(animation->get_track_count() - 1, np); animation->remove_track(animation->get_track_count() - 1); // Hack. switch (h.type) { - case Variant::FLOAT: { -#ifdef DISABLE_DEPRECATED - bool is_angle = h.type == Variant::FLOAT && h.hint_string.contains("radians_as_degrees"); -#else - bool is_angle = h.type == Variant::FLOAT && h.hint_string.contains("radians"); -#endif // DISABLE_DEPRECATED - if (is_angle) { - *r_interpolation_type = Animation::INTERPOLATION_LINEAR_ANGLE; - } - [[fallthrough]]; - } + case Variant::FLOAT: case Variant::VECTOR2: case Variant::RECT2: case Variant::VECTOR3: diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index 545404ec8e..b62351256e 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -55,11 +55,6 @@ void EditorAutoloadSettings::_notification(int p_what) { file_dialog->add_filter("*." + E); } - for (const AutoloadInfo &info : autoload_cache) { - if (info.node && info.in_editor) { - callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED); - } - } browse_button->set_icon(get_editor_theme_icon(SNAME("Folder"))); } break; @@ -419,6 +414,8 @@ Node *EditorAutoloadSettings::_create_autoload(const String &p_path) { Ref<Script> scr = res; if (scr.is_valid()) { + ERR_FAIL_COND_V_MSG(!scr->is_valid(), nullptr, vformat("Failed to create an autoload, script '%s' is not compiling.", p_path)); + StringName ibt = scr->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "Node"); ERR_FAIL_COND_V_MSG(!valid_type, nullptr, vformat("Failed to create an autoload, script '%s' does not inherit from 'Node'.", p_path)); @@ -436,6 +433,35 @@ Node *EditorAutoloadSettings::_create_autoload(const String &p_path) { return n; } +void EditorAutoloadSettings::init_autoloads() { + for (AutoloadInfo &info : autoload_cache) { + info.node = _create_autoload(info.path); + + if (info.node) { + Ref<Script> scr = info.node->get_script(); + info.in_editor = scr.is_valid() && scr->is_tool(); + info.node->set_name(info.name); + } + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_named_global_constant(info.name, info.node); + } + } + + if (!info.is_singleton && !info.in_editor && info.node != nullptr) { + memdelete(info.node); + info.node = nullptr; + } + } + + for (const AutoloadInfo &info : autoload_cache) { + if (info.node && info.in_editor) { + callable_mp((Node *)get_tree()->get_root(), &Node::add_child).call_deferred(info.node, false, Node::INTERNAL_MODE_DISABLED); + } + } +} + void EditorAutoloadSettings::update_autoload() { if (updating_autoload) { return; @@ -857,27 +883,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() { autoload_cache.push_back(info); } - for (AutoloadInfo &info : autoload_cache) { - info.node = _create_autoload(info.path); - - if (info.node) { - Ref<Script> scr = info.node->get_script(); - info.in_editor = scr.is_valid() && scr->is_tool(); - info.node->set_name(info.name); - } - - if (info.is_singleton) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_named_global_constant(info.name, info.node); - } - } - - if (!info.is_singleton && !info.in_editor && info.node != nullptr) { - memdelete(info.node); - info.node = nullptr; - } - } - HBoxContainer *hbc = memnew(HBoxContainer); add_child(hbc); diff --git a/editor/editor_autoload_settings.h b/editor/editor_autoload_settings.h index e4ac62e700..2ab969eb59 100644 --- a/editor/editor_autoload_settings.h +++ b/editor/editor_autoload_settings.h @@ -104,6 +104,7 @@ protected: static void _bind_methods(); public: + void init_autoloads(); void update_autoload(); bool autoload_add(const String &p_name, const String &p_path); void autoload_remove(const String &p_name); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index d84ccb0c03..f0dc850af0 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -44,6 +44,7 @@ #include "editor/editor_paths.h" #include "editor/editor_resource_preview.h" #include "editor/editor_settings.h" +#include "editor/project_settings_editor.h" #include "scene/resources/packed_scene.h" EditorFileSystem *EditorFileSystem::singleton = nullptr; @@ -206,17 +207,68 @@ EditorFileSystemDirectory::EditorFileSystemDirectory() { } EditorFileSystemDirectory::~EditorFileSystemDirectory() { - for (int i = 0; i < files.size(); i++) { - memdelete(files[i]); + for (EditorFileSystemDirectory::FileInfo *fi : files) { + memdelete(fi); } - for (int i = 0; i < subdirs.size(); i++) { - memdelete(subdirs[i]); + for (EditorFileSystemDirectory *dir : subdirs) { + memdelete(dir); + } +} + +EditorFileSystem::ScannedDirectory::~ScannedDirectory() { + for (ScannedDirectory *dir : subdirs) { + memdelete(dir); + } +} + +void EditorFileSystem::_first_scan_filesystem() { + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); + first_scan_root_dir = memnew(ScannedDirectory); + first_scan_root_dir->full_path = "res://"; + HashSet<String> existing_class_names; + + nb_files_total = _scan_new_dir(first_scan_root_dir, d); + + // This loads the global class names from the scripts and ensures that even if the + // global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer. + _first_scan_process_scripts(first_scan_root_dir, existing_class_names); + + // Removing invalid global class to prevent having invalid paths in ScriptServer. + _remove_invalid_global_class_names(existing_class_names); + + // Now that all the global class names should be loaded, create autoloads and plugins. + // This is done after loading the global class names because autoloads and plugins can use + // global class names. + ProjectSettingsEditor::get_singleton()->init_autoloads(); + EditorNode::get_singleton()->init_plugins(); +} + +void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) { + for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { + _first_scan_process_scripts(scan_sub_dir, p_existing_class_names); + } + + for (const String &scan_file : p_scan_dir->files) { + String path = p_scan_dir->full_path.path_join(scan_file); + String type = ResourceLoader::get_resource_type(path); + + if (ClassDB::is_parent_class(type, SNAME("Script"))) { + String script_class_extends; + String script_class_icon_path; + String script_class_name = _get_global_script_class(type, path, &script_class_extends, &script_class_icon_path); + _register_global_class_script(path, path, type, script_class_name, script_class_extends, script_class_icon_path); + + if (!script_class_name.is_empty()) { + p_existing_class_names.insert(script_class_name); + } + } } } void EditorFileSystem::_scan_filesystem() { - ERR_FAIL_COND(!scanning || new_filesystem); + // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem. + ERR_FAIL_COND(!scanning || new_filesystem || (first_scan && !first_scan_root_dir)); //read .fscache String cpath; @@ -318,23 +370,33 @@ void EditorFileSystem::_scan_filesystem() { } EditorProgressBG scan_progress("efs", "ScanFS", 1000); - ScanProgress sp; - sp.low = 0; - sp.hi = 1; + sp.hi = nb_files_total; sp.progress = &scan_progress; new_filesystem = memnew(EditorFileSystemDirectory); new_filesystem->parent = nullptr; - Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); - d->change_dir("res://"); - _scan_new_dir(new_filesystem, d, sp); - dep_update_list.clear(); + ScannedDirectory *sd; + // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem. + if (first_scan) { + sd = first_scan_root_dir; + } else { + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); + sd = memnew(ScannedDirectory); + sd->full_path = "res://"; + nb_files_total = _scan_new_dir(sd, d); + } + + _process_file_system(sd, new_filesystem, sp); + dep_update_list.clear(); file_cache.clear(); //clear caches, no longer needed - if (!first_scan) { + if (first_scan) { + memdelete(first_scan_root_dir); + first_scan_root_dir = nullptr; + } else { //on the first scan this is done from the main thread after re-importing _save_filesystem_cache(); } @@ -567,6 +629,10 @@ bool EditorFileSystem::_scan_import_support(const Vector<String> &reimports) { bool EditorFileSystem::_update_scan_actions() { sources_changed.clear(); + // We need to update the script global class names before the reimports to be sure that + // all the importer classes that depends on class names will work. + _update_script_classes(); + bool fs_changed = false; Vector<String> reimports; @@ -615,7 +681,7 @@ bool EditorFileSystem::_update_scan_actions() { fs_changed = true; if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) { - _queue_update_script_class(ia.dir->get_file_path(idx)); + _queue_update_script_class(ia.dir->get_file_path(idx), ia.new_file->type, ia.new_file->script_class_name, ia.new_file->script_class_extends, ia.new_file->script_class_icon_path); } if (ia.new_file->type == SNAME("PackedScene")) { _queue_update_scene_groups(ia.dir->get_file_path(idx)); @@ -626,8 +692,9 @@ bool EditorFileSystem::_update_scan_actions() { int idx = ia.dir->find_file_index(ia.file); ERR_CONTINUE(idx == -1); + String script_class_name = ia.dir->files[idx]->script_class_name; if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) { - _queue_update_script_class(ia.dir->get_file_path(idx)); + _queue_update_script_class(ia.dir->get_file_path(idx), "", "", "", ""); } if (ia.dir->files[idx]->type == SNAME("PackedScene")) { _queue_update_scene_groups(ia.dir->get_file_path(idx)); @@ -637,6 +704,15 @@ bool EditorFileSystem::_update_scan_actions() { memdelete(ia.dir->files[idx]); ia.dir->files.remove_at(idx); + // Restore another script with the same global class name if it exists. + if (!script_class_name.is_empty()) { + EditorFileSystemDirectory::FileInfo *old_fi = nullptr; + String old_file = _get_file_by_class_name(filesystem, script_class_name, old_fi); + if (!old_file.is_empty() && old_fi) { + _queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path); + } + } + fs_changed = true; } break; @@ -667,10 +743,11 @@ bool EditorFileSystem::_update_scan_actions() { ERR_CONTINUE(idx == -1); String full_path = ia.dir->get_file_path(idx); - if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) { - _queue_update_script_class(full_path); + const EditorFileSystemDirectory::FileInfo *fi = ia.dir->files[idx]; + if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) { + _queue_update_script_class(full_path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path); } - if (ia.dir->files[idx]->type == SNAME("PackedScene")) { + if (fi->type == SNAME("PackedScene")) { _queue_update_scene_groups(full_path); } @@ -711,6 +788,10 @@ bool EditorFileSystem::_update_scan_actions() { _save_filesystem_cache(); } + // Moving the processing of pending updates before the resources_reload event to be sure all global class names + // are updated. Script.cpp listens on resources_reload and reloads updated scripts. + _process_update_pending(); + if (reloads.size()) { emit_signal(SNAME("resources_reload"), reloads); } @@ -728,6 +809,14 @@ void EditorFileSystem::scan() { return; } + // The first scan must be on the main thread because, after the first scan and update + // of global class names, we load the plugins and autoloads. These need to + // be added on the main thread because they are nodes, and we need to wait for them + // to be loaded to continue the scan and reimportations. + if (first_scan) { + _first_scan_filesystem(); + } + _update_extensions(); if (!use_threads) { @@ -741,14 +830,14 @@ void EditorFileSystem::scan() { filesystem = new_filesystem; new_filesystem = nullptr; _update_scan_actions(); - scanning = false; - _update_pending_script_classes(); - _update_pending_scene_groups(); // Update all icons so they are loaded for the FileSystemDock. _update_files_icon_path(); + scanning = false; + // Set first_scan to false before the signals so the function doing_first_scan can return false + // in editor_node to start the export if needed. + first_scan = false; emit_signal(SNAME("filesystem_changed")); emit_signal(SNAME("sources_changed"), sources_changed.size() > 0); - first_scan = false; } else { ERR_FAIL_COND(thread.is_started()); set_process(true); @@ -762,28 +851,19 @@ void EditorFileSystem::scan() { } } -void EditorFileSystem::ScanProgress::update(int p_current, int p_total) const { - float ratio = low + ((hi - low) / p_total) * p_current; - progress->step(ratio * 1000); +void EditorFileSystem::ScanProgress::increment() { + current++; + float ratio = current / MAX(hi, 1.0f); + progress->step(ratio * 1000.0f); EditorFileSystem::singleton->scan_total = ratio; } -EditorFileSystem::ScanProgress EditorFileSystem::ScanProgress::get_sub(int p_current, int p_total) const { - ScanProgress sp = *this; - float slice = (sp.hi - sp.low) / p_total; - sp.low += slice * p_current; - sp.hi = slice; - return sp; -} - -void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress) { +int EditorFileSystem::_scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da) { List<String> dirs; List<String> files; String cd = da->get_current_dir(); - p_dir->modified_time = FileAccess::get_modified_time(cd); - da->list_dir_begin(); while (true) { String f = da->get_next(); @@ -816,55 +896,59 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc dirs.sort_custom<FileNoCaseComparator>(); files.sort_custom<FileNoCaseComparator>(); - int total = dirs.size() + files.size(); - int idx = 0; + int nb_files_total_scan = 0; - for (List<String>::Element *E = dirs.front(); E; E = E->next(), idx++) { + for (List<String>::Element *E = dirs.front(); E; E = E->next()) { if (da->change_dir(E->get()) == OK) { String d = da->get_current_dir(); if (d == cd || !d.begins_with(cd)) { da->change_dir(cd); //avoid recursion } else { - EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory); + ScannedDirectory *sd = memnew(ScannedDirectory); + sd->name = E->get(); + sd->full_path = p_dir->full_path.path_join(sd->name); - efd->parent = p_dir; - efd->name = E->get(); + nb_files_total_scan += _scan_new_dir(sd, da); - _scan_new_dir(efd, da, p_progress.get_sub(idx, total)); - - int idx2 = 0; - for (int i = 0; i < p_dir->subdirs.size(); i++) { - if (efd->name.filenocasecmp_to(p_dir->subdirs[i]->name) < 0) { - break; - } - idx2++; - } - if (idx2 == p_dir->subdirs.size()) { - p_dir->subdirs.push_back(efd); - } else { - p_dir->subdirs.insert(idx2, efd); - } + p_dir->subdirs.push_back(sd); da->change_dir(".."); } } else { ERR_PRINT("Cannot go into subdir '" + E->get() + "'."); } + } - p_progress.update(idx, total); + p_dir->files = files; + nb_files_total_scan += files.size(); + + return nb_files_total_scan; +} + +void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { + p_dir->modified_time = FileAccess::get_modified_time(p_scan_dir->full_path); + + for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { + EditorFileSystemDirectory *sub_dir = memnew(EditorFileSystemDirectory); + sub_dir->parent = p_dir; + sub_dir->name = scan_sub_dir->name; + p_dir->subdirs.push_back(sub_dir); + _process_file_system(scan_sub_dir, sub_dir, p_progress); } - for (List<String>::Element *E = files.front(); E; E = E->next(), idx++) { - String ext = E->get().get_extension().to_lower(); + for (const String &scan_file : p_scan_dir->files) { + String ext = scan_file.get_extension().to_lower(); if (!valid_extensions.has(ext)) { + p_progress.increment(); continue; //invalid } - EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); - fi->file = E->get(); + String path = p_scan_dir->full_path.path_join(scan_file); - String path = cd.path_join(fi->file); + EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); + fi->file = scan_file; + p_dir->files.push_back(fi); FileCache *fc = file_cache.getptr(path); uint64_t mt = FileAccess::get_modified_time(path); @@ -894,7 +978,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc ItemAction ia; ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; ia.dir = p_dir; - ia.file = E->get(); + ia.file = fi->file; scan_actions.push_back(ia); } @@ -923,7 +1007,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc ItemAction ia; ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; ia.dir = p_dir; - ia.file = E->get(); + ia.file = fi->file; scan_actions.push_back(ia); } } else { @@ -939,6 +1023,21 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc fi->script_class_name = fc->script_class_name; fi->script_class_extends = fc->script_class_extends; fi->script_class_icon_path = fc->script_class_icon_path; + + if (first_scan && ClassDB::is_parent_class(fi->type, SNAME("Script"))) { + bool update_script = false; + String old_class_name = fi->script_class_name; + fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); + if (old_class_name != fi->script_class_name) { + update_script = true; + } else if (!fi->script_class_name.is_empty() && (!ScriptServer::is_global_class(fi->script_class_name) || ScriptServer::get_global_class_path(fi->script_class_name) != path)) { + // This script has a class name but is not in the global class names or the path of the class has changed. + update_script = true; + } + if (update_script) { + _queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path); + } + } } else { //new or modified time fi->type = ResourceLoader::get_resource_type(path); @@ -956,7 +1055,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc // Files in dep_update_list are forced for rescan to update dependencies. They don't need other updates. if (!dep_update_list.has(path)) { if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) { - _queue_update_script_class(path); + _queue_update_script_class(path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path); } if (fi->type == SNAME("PackedScene")) { _queue_update_scene_groups(path); @@ -973,16 +1072,16 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc } } - p_dir->files.push_back(fi); - p_progress.update(idx, total); + p_progress.increment(); } } -void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress) { +void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path()); bool updated_dir = false; String cd = p_dir->get_path(); + int diff_nb_files = 0; if (current_mtime != p_dir->modified_time || using_fat32_or_exfat) { updated_dir = true; @@ -999,6 +1098,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const p_dir->get_subdir(i)->verified = false; } + diff_nb_files -= p_dir->files.size(); + //then scan files and directories and check what's different Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); @@ -1024,17 +1125,25 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const int idx = p_dir->find_dir_index(f); if (idx == -1) { - if (_should_skip_directory(cd.path_join(f))) { + String dir_path = cd.path_join(f); + if (_should_skip_directory(dir_path)) { continue; } - EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory); + ScannedDirectory sd; + sd.name = f; + sd.full_path = dir_path; + EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory); efd->parent = p_dir; efd->name = f; + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); - d->change_dir(cd.path_join(f)); - _scan_new_dir(efd, d, p_progress.get_sub(1, 1)); + d->change_dir(dir_path); + int nb_files_dir = _scan_new_dir(&sd, d); + p_progress.hi += nb_files_dir; + diff_nb_files += nb_files_dir; + _process_file_system(&sd, efd, p_progress); ItemAction ia; ia.action = ItemAction::ACTION_DIR_ADD; @@ -1088,7 +1197,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ia.file = f; scan_actions.push_back(ia); } - + diff_nb_files++; } else { p_dir->files[idx]->verified = true; } @@ -1106,6 +1215,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const ia.dir = p_dir; ia.file = p_dir->files[i]->file; scan_actions.push_back(ia); + diff_nb_files--; continue; } @@ -1152,10 +1262,16 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const scan_actions.push_back(ia); } } + + p_progress.increment(); } for (int i = 0; i < p_dir->subdirs.size(); i++) { if ((updated_dir && !p_dir->subdirs[i]->verified) || _should_skip_directory(p_dir->subdirs[i]->get_path())) { + // Add all the files of the folder to be sure _update_scan_actions process the removed files + // for global class names. + diff_nb_files += _insert_actions_delete_files_directory(p_dir->subdirs[i]); + //this directory was removed or ignored, add action to remove it ItemAction ia; ia.action = ItemAction::ACTION_DIR_REMOVE; @@ -1165,6 +1281,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const } _scan_fs_changes(p_dir->get_subdir(i), p_progress); } + + nb_files_total = MAX(nb_files_total + diff_nb_files, 0); } void EditorFileSystem::_delete_internal_files(const String &p_file) { @@ -1179,19 +1297,64 @@ void EditorFileSystem::_delete_internal_files(const String &p_file) { } } +int EditorFileSystem::_insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir) { + int nb_files = 0; + for (EditorFileSystemDirectory::FileInfo *fi : p_dir->files) { + ItemAction ia; + ia.action = ItemAction::ACTION_FILE_REMOVE; + ia.dir = p_dir; + ia.file = fi->file; + scan_actions.push_back(ia); + nb_files++; + } + + for (EditorFileSystemDirectory *sub_dir : p_dir->subdirs) { + nb_files += _insert_actions_delete_files_directory(sub_dir); + } + + return nb_files; +} + void EditorFileSystem::_thread_func_sources(void *_userdata) { EditorFileSystem *efs = (EditorFileSystem *)_userdata; if (efs->filesystem) { EditorProgressBG pr("sources", TTR("ScanSources"), 1000); ScanProgress sp; sp.progress = ≺ - sp.hi = 1; - sp.low = 0; + sp.hi = efs->nb_files_total; efs->_scan_fs_changes(efs->filesystem, sp); } efs->scanning_changes_done.set(); } +void EditorFileSystem::_remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names) { + List<StringName> global_classes; + ScriptServer::get_global_class_list(&global_classes); + for (const StringName &class_name : global_classes) { + if (!p_existing_class_names.has(class_name)) { + ScriptServer::remove_global_class(class_name); + } + } +} + +String EditorFileSystem::_get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info) { + for (EditorFileSystemDirectory::FileInfo *fi : p_dir->files) { + if (fi->script_class_name == p_class_name) { + r_file_info = fi; + return p_dir->get_path().path_join(fi->file); + } + } + + for (EditorFileSystemDirectory *sub_dir : p_dir->subdirs) { + String file = _get_file_by_class_name(sub_dir, p_class_name, r_file_info); + if (!file.is_empty()) { + return file; + } + } + r_file_info = nullptr; + return ""; +} + void EditorFileSystem::scan_changes() { if (first_scan || // Prevent a premature changes scan from inhibiting the first full scan scanning || scanning_changes || thread.is_started()) { @@ -1210,14 +1373,10 @@ void EditorFileSystem::scan_changes() { EditorProgressBG pr("sources", TTR("ScanSources"), 1000); ScanProgress sp; sp.progress = ≺ - sp.hi = 1; - sp.low = 0; + sp.hi = nb_files_total; scan_total = 0; _scan_fs_changes(filesystem, sp); - bool changed = _update_scan_actions(); - _update_pending_script_classes(); - _update_pending_scene_groups(); - if (changed) { + if (_update_scan_actions()) { emit_signal(SNAME("filesystem_changed")); } } @@ -1282,13 +1441,13 @@ void EditorFileSystem::_notification(int p_what) { thread_sources.wait_to_finish(); } bool changed = _update_scan_actions(); - _update_pending_script_classes(); - _update_pending_scene_groups(); + // Set first_scan to false before the signals so the function doing_first_scan can return false + // in editor_node to start the export if needed. + first_scan = false; if (changed) { emit_signal(SNAME("filesystem_changed")); } emit_signal(SNAME("sources_changed"), sources_changed.size() > 0); - first_scan = false; scanning_changes = false; // Changed to false here to prevent recursive triggering of scan thread. done_importing = true; } @@ -1302,13 +1461,13 @@ void EditorFileSystem::_notification(int p_what) { new_filesystem = nullptr; thread.wait_to_finish(); _update_scan_actions(); - _update_pending_script_classes(); - _update_pending_scene_groups(); // Update all icons so they are loaded for the FileSystemDock. _update_files_icon_path(); + // Set first_scan to false before the signals so the function doing_first_scan can return false + // in editor_node to start the export if needed. + first_scan = false; emit_signal(SNAME("filesystem_changed")); emit_signal(SNAME("sources_changed"), sources_changed.size() > 0); - first_scan = false; } if (done_importing && scan_changes_pending) { @@ -1323,7 +1482,7 @@ void EditorFileSystem::_notification(int p_what) { } bool EditorFileSystem::is_scanning() const { - return scanning || scanning_changes; + return scanning || scanning_changes || first_scan; } float EditorFileSystem::get_scanning_progress() const { @@ -1624,14 +1783,41 @@ void EditorFileSystem::_update_files_icon_path(EditorFileSystemDirectory *edp) { } void EditorFileSystem::_update_script_classes() { + if (update_script_paths.is_empty()) { + return; + } + update_script_mutex.lock(); - for (const String &path : update_script_paths) { - EditorFileSystem::get_singleton()->register_global_class_script(path, path); + for (const KeyValue<String, ScriptInfo> &E : update_script_paths) { + _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); } - // Parse documentation second, as it requires the class names to be correct and registered - for (const String &path : update_script_paths) { + update_script_paths.clear(); + update_script_mutex.unlock(); + + ScriptServer::save_global_classes(); + EditorNode::get_editor_data().script_class_save_icon_paths(); + + emit_signal("script_classes_updated"); + + // Rescan custom loaders and savers. + // Doing the following here because the `filesystem_changed` signal fires multiple times and isn't always followed by script classes update. + // So I thought it's better to do this when script classes really get updated + ResourceLoader::remove_custom_loaders(); + ResourceLoader::add_custom_loaders(); + ResourceSaver::remove_custom_savers(); + ResourceSaver::add_custom_savers(); +} + +void EditorFileSystem::_update_script_documentation() { + if (update_script_paths_documentation.is_empty()) { + return; + } + + update_script_mutex.lock(); + + for (const String &path : update_script_paths_documentation) { int index = -1; EditorFileSystemDirectory *efd = find_file(path, &index); @@ -1655,40 +1841,38 @@ void EditorFileSystem::_update_script_classes() { } } - update_script_paths.clear(); + update_script_paths_documentation.clear(); update_script_mutex.unlock(); - - ScriptServer::save_global_classes(); - EditorNode::get_editor_data().script_class_save_icon_paths(); - emit_signal("script_classes_updated"); - - // Rescan custom loaders and savers. - // Doing the following here because the `filesystem_changed` signal fires multiple times and isn't always followed by script classes update. - // So I thought it's better to do this when script classes really get updated - ResourceLoader::remove_custom_loaders(); - ResourceLoader::add_custom_loaders(); - ResourceSaver::remove_custom_savers(); - ResourceSaver::add_custom_savers(); } -void EditorFileSystem::_update_pending_script_classes() { - if (!update_script_paths.is_empty()) { - _update_script_classes(); - } else { - // In case the class cache file was removed somehow, regenerate it. - if (!FileAccess::exists(ScriptServer::get_global_class_cache_file_path())) { - ScriptServer::save_global_classes(); - } - } +void EditorFileSystem::_process_update_pending() { + _update_script_classes(); + // Parse documentation second, as it requires the class names to be loaded + // because _update_script_documentation loads the scripts completely. + _update_script_documentation(); + _update_pending_scene_groups(); } -void EditorFileSystem::_queue_update_script_class(const String &p_path) { +void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) { update_script_mutex.lock(); - update_script_paths.insert(p_path); + + ScriptInfo si; + si.type = p_type; + si.script_class_name = p_script_class_name; + si.script_class_extends = p_script_class_extends; + si.script_class_icon_path = p_script_class_icon_path; + update_script_paths.insert(p_path, si); + + update_script_paths_documentation.insert(p_path); + update_script_mutex.unlock(); } void EditorFileSystem::_update_scene_groups() { + if (update_scene_paths.is_empty()) { + return; + } + EditorProgress *ep = nullptr; if (update_scene_paths.size() > 1) { ep = memnew(EditorProgress("update_scene_groups", TTR("Update Scene Groups"), update_scene_paths.size())); @@ -1787,7 +1971,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { } } if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(file); + _queue_update_script_class(file, fs->files[cpos]->type, "", "", ""); if (!fs->files[cpos]->script_class_icon_path.is_empty()) { update_files_icon_cache = true; } @@ -1840,6 +2024,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { } const String old_script_class_icon_path = fs->files[cpos]->script_class_icon_path; + const String old_class_name = fs->files[cpos]->script_class_name; fs->files[cpos]->type = type; fs->files[cpos]->resource_script_class = script_class; fs->files[cpos]->uid = uid; @@ -1862,23 +2047,32 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { EditorResourcePreview::get_singleton()->check_for_invalidation(file); if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) { - _queue_update_script_class(file); + _queue_update_script_class(file, fs->files[cpos]->type, fs->files[cpos]->script_class_name, fs->files[cpos]->script_class_extends, fs->files[cpos]->script_class_icon_path); } if (fs->files[cpos]->type == SNAME("PackedScene")) { _queue_update_scene_groups(file); } + if (fs->files[cpos]->type == SNAME("Resource")) { files_to_update_icon_path.push_back(fs->files[cpos]); } else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) { update_files_icon_cache = true; } + + // Restore another script as the global class name if multiple scripts had the same old class name. + if (!old_class_name.is_empty() && fs->files[cpos]->script_class_name != old_class_name && ClassDB::is_parent_class(type, SNAME("Script"))) { + EditorFileSystemDirectory::FileInfo *old_fi = nullptr; + String old_file = _get_file_by_class_name(filesystem, old_class_name, old_fi); + if (!old_file.is_empty() && old_fi) { + _queue_update_script_class(old_file, old_fi->type, old_fi->script_class_name, old_fi->script_class_extends, old_fi->script_class_icon_path); + } + } updated = true; } } if (updated) { - _update_pending_script_classes(); - _update_pending_scene_groups(); + _process_update_pending(); if (update_files_icon_cache) { _update_files_icon_path(); } else { @@ -1894,31 +2088,37 @@ HashSet<String> EditorFileSystem::get_valid_extensions() const { return valid_extensions; } -void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) { +void EditorFileSystem::_register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) { ScriptServer::remove_global_class_by_path(p_search_path); // First remove, just in case it changed - int index = -1; - EditorFileSystemDirectory *efd = find_file(p_search_path, &index); - - if (!efd || index < 0) { - // The file was removed + if (p_script_class_name.is_empty()) { return; } - if (!efd->files[index]->script_class_name.is_empty()) { - String lang; - for (int j = 0; j < ScriptServer::get_language_count(); j++) { - if (ScriptServer::get_language(j)->handles_global_class_type(efd->files[index]->type)) { - lang = ScriptServer::get_language(j)->get_name(); - } - } - if (lang.is_empty()) { - return; // No lang found that can handle this global class + String lang; + for (int j = 0; j < ScriptServer::get_language_count(); j++) { + if (ScriptServer::get_language(j)->handles_global_class_type(p_type)) { + lang = ScriptServer::get_language(j)->get_name(); + break; } + } + if (lang.is_empty()) { + return; // No lang found that can handle this global class + } - ScriptServer::add_global_class(efd->files[index]->script_class_name, efd->files[index]->script_class_extends, lang, p_target_path); - EditorNode::get_editor_data().script_class_set_icon_path(efd->files[index]->script_class_name, efd->files[index]->script_class_icon_path); - EditorNode::get_editor_data().script_class_set_name(p_target_path, efd->files[index]->script_class_name); + ScriptServer::add_global_class(p_script_class_name, p_script_class_extends, lang, p_target_path); + EditorNode::get_editor_data().script_class_set_icon_path(p_script_class_name, p_script_class_icon_path); + EditorNode::get_editor_data().script_class_set_name(p_target_path, p_script_class_name); +} + +void EditorFileSystem::register_global_class_script(const String &p_search_path, const String &p_target_path) { + int index_file; + EditorFileSystemDirectory *efsd = find_file(p_search_path, &index_file); + if (efsd) { + const EditorFileSystemDirectory::FileInfo *fi = efsd->files[index_file]; + EditorFileSystem::get_singleton()->_register_global_class_script(p_search_path, p_target_path, fi->type, fi->script_class_name, fi->script_class_extends, fi->script_class_icon_path); + } else { + ScriptServer::remove_global_class_by_path(p_search_path); } } @@ -2542,8 +2742,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) { ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache. _save_filesystem_cache(); - _update_pending_script_classes(); - _update_pending_scene_groups(); + _process_update_pending(); importing = false; if (!is_scanning()) { emit_signal(SNAME("filesystem_changed")); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 88cd67296b..b0c6f0de51 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -159,11 +159,21 @@ class EditorFileSystem : public Node { EditorFileSystemDirectory::FileInfo *new_file = nullptr; }; + struct ScannedDirectory { + String name; + String full_path; + Vector<ScannedDirectory *> subdirs; + List<String> files; + + ~ScannedDirectory(); + }; + bool use_threads = false; Thread thread; static void _thread_func(void *_userdata); EditorFileSystemDirectory *new_filesystem = nullptr; + ScannedDirectory *first_scan_root_dir = nullptr; bool scanning = false; bool importing = false; @@ -172,8 +182,11 @@ class EditorFileSystem : public Node { float scan_total; String filesystem_settings_version_for_import; bool revalidate_import_files = false; + int nb_files_total = 0; void _scan_filesystem(); + void _first_scan_filesystem(); + void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names); HashSet<String> late_update_files; @@ -202,11 +215,10 @@ class EditorFileSystem : public Node { HashSet<String> dep_update_list; struct ScanProgress { - float low = 0; float hi = 0; - mutable EditorProgressBG *progress = nullptr; - void update(int p_current, int p_total) const; - ScanProgress get_sub(int p_current, int p_total) const; + int current = 0; + EditorProgressBG *progress = nullptr; + void increment(); }; void _save_filesystem_cache(); @@ -214,15 +226,17 @@ class EditorFileSystem : public Node { bool _find_file(const String &p_file, EditorFileSystemDirectory **r_d, int &r_file_pos) const; - void _scan_fs_changes(EditorFileSystemDirectory *p_dir, const ScanProgress &p_progress); + void _scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress); void _delete_internal_files(const String &p_file); + int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir); HashSet<String> textfile_extensions; HashSet<String> valid_extensions; HashSet<String> import_extensions; - void _scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAccess> &da, const ScanProgress &p_progress); + int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da); + void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress); Thread thread_sources; bool scanning_changes = false; @@ -256,11 +270,20 @@ class EditorFileSystem : public Node { } }; + struct ScriptInfo { + String type; + String script_class_name; + String script_class_extends; + String script_class_icon_path; + }; + Mutex update_script_mutex; - HashSet<String> update_script_paths; - void _queue_update_script_class(const String &p_path); + HashMap<String, ScriptInfo> update_script_paths; + HashSet<String> update_script_paths_documentation; + void _queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path); void _update_script_classes(); - void _update_pending_script_classes(); + void _update_script_documentation(); + void _process_update_pending(); Mutex update_scene_mutex; HashSet<String> update_scene_paths; @@ -300,6 +323,10 @@ class EditorFileSystem : public Node { void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info); void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr); + void _remove_invalid_global_class_names(const HashSet<String> &p_existing_class_names); + String _get_file_by_class_name(EditorFileSystemDirectory *p_dir, const String &p_class_name, EditorFileSystemDirectory::FileInfo *&r_file_info); + + void _register_global_class_script(const String &p_search_path, const String &p_target_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path); protected: void _notification(int p_what); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ae7066fea9..4323025770 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -706,23 +706,7 @@ void EditorNode::_notification(int p_what) { } break; case NOTIFICATION_READY: { - { - started_timestamp = Time::get_singleton()->get_unix_time_from_system(); - _initializing_plugins = true; - Vector<String> addons; - if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) { - addons = GLOBAL_GET("editor_plugins/enabled"); - } - - for (int i = 0; i < addons.size(); i++) { - set_addon_plugin_enabled(addons[i], true); - } - _initializing_plugins = false; - - if (!pending_addons.is_empty()) { - EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons)); - } - } + started_timestamp = Time::get_singleton()->get_unix_time_from_system(); RenderingServer::get_singleton()->viewport_set_disable_2d(get_scene_root()->get_viewport_rid(), true); RenderingServer::get_singleton()->viewport_set_environment_mode(get_viewport()->get_viewport_rid(), RenderingServer::VIEWPORT_ENVIRONMENT_DISABLED); @@ -860,6 +844,23 @@ void EditorNode::_update_update_spinner() { OS::get_singleton()->set_low_processor_usage_mode(!update_continuously); } +void EditorNode::init_plugins() { + _initializing_plugins = true; + Vector<String> addons; + if (ProjectSettings::get_singleton()->has_setting("editor_plugins/enabled")) { + addons = GLOBAL_GET("editor_plugins/enabled"); + } + + for (const String &addon : addons) { + set_addon_plugin_enabled(addon, true); + } + _initializing_plugins = false; + + if (!pending_addons.is_empty()) { + EditorFileSystem::get_singleton()->connect("script_classes_updated", callable_mp(this, &EditorNode::_enable_pending_addons), CONNECT_ONE_SHOT); + } +} + void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) { Ref<Script> scr = Object::cast_to<Script>(p_script); if (scr.is_null()) { @@ -954,6 +955,7 @@ void EditorNode::_fs_changed() { // FIXME: Move this to a cleaner location, it's hacky to do this in _fs_changed. String export_error; Error err = OK; + // It's important to wait for the first scan to finish; otherwise, scripts or resources might not be imported. if (!export_defer.preset.is_empty() && !EditorFileSystem::get_singleton()->is_scanning()) { String preset_name = export_defer.preset; // Ensures export_project does not loop infinitely, because notifications may diff --git a/editor/editor_node.h b/editor/editor_node.h index 49674dd1c1..28bd692ddf 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -683,6 +683,7 @@ protected: public: // Public for use with callable_mp. + void init_plugins(); void _on_plugin_ready(Object *p_script, const String &p_activate_name); void editor_select(int p_which); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 29ca1279b2..d4bd97a393 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -546,7 +546,7 @@ void FileSystemDock::_notification(int p_what) { case NOTIFICATION_PROCESS: { if (EditorFileSystem::get_singleton()->is_scanning()) { - scanning_progress->set_value(EditorFileSystem::get_singleton()->get_scanning_progress() * 100); + scanning_progress->set_value(EditorFileSystem::get_singleton()->get_scanning_progress() * 100.0f); } } break; diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index 005407e188..d9f60e155d 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -414,13 +414,19 @@ void EditorPlugin::remove_translation_parser_plugin(const Ref<EditorTranslationP void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer, bool p_first_priority) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceFormatImporter::get_singleton()->add_importer(p_importer, p_first_priority); - callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred(); + // Plugins are now loaded during the first scan. It's important not to start another scan, + // even a deferred one, as it would cause a scan during a scan at the next main thread iteration. + if (!EditorFileSystem::get_singleton()->doing_first_scan()) { + callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred(); + } } void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) { ERR_FAIL_COND(!p_importer.is_valid()); ResourceFormatImporter::get_singleton()->remove_importer(p_importer); - if (!EditorNode::get_singleton()->is_exiting()) { + // Plugins are now loaded during the first scan. It's important not to start another scan, + // even a deferred one, as it would cause a scan during a scan at the next main thread iteration. + if (!EditorNode::get_singleton()->is_exiting() && !EditorFileSystem::get_singleton()->doing_first_scan()) { callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred(); } } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index c2d89b28f5..943e345e97 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -91,6 +91,10 @@ void ProjectSettingsEditor::update_plugins() { plugin_settings->update_plugins(); } +void ProjectSettingsEditor::init_autoloads() { + autoload_settings->init_autoloads(); +} + void ProjectSettingsEditor::_setting_edited(const String &p_name) { queue_save(); } diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index fb294c6bbd..5890ed2c2d 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -123,6 +123,7 @@ public: void set_plugins_page(); void set_general_page(const String &p_category); void update_plugins(); + void init_autoloads(); EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; } GroupSettingsEditor *get_group_settings() { return group_settings; } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 7f60fbbdb9..94bd3e16d3 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -77,12 +77,12 @@ void SceneTreeDock::_quick_open() { void SceneTreeDock::_inspect_hovered_node() { select_node_hovered_at_end_of_drag = true; - if (tree_item_inspected != nullptr) { - tree_item_inspected->clear_custom_color(0); - } Tree *tree = scene_tree->get_scene_tree(); TreeItem *item = tree->get_item_with_metadata(node_hovered_now->get_path()); if (item) { + if (tree_item_inspected) { + tree_item_inspected->clear_custom_color(0); + } tree_item_inspected = item; tree_item_inspected->set_custom_color(0, get_theme_color(SNAME("accent_color"), EditorStringName(Editor))); } @@ -133,8 +133,9 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) { } if (mb->is_released()) { - if (tree_item_inspected != nullptr) { + if (tree_item_inspected) { tree_item_inspected->clear_custom_color(0); + tree_item_inspected = nullptr; } _reset_hovering_timer(); } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 20d424894a..73b1e44db3 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -115,7 +115,7 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p } GDScriptFunction *GDScript::_super_constructor(GDScript *p_script) { - if (p_script->initializer) { + if (likely(p_script->valid) && p_script->initializer) { return p_script->initializer; } else { GDScript *base_src = p_script->_base; @@ -136,7 +136,11 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance } } ERR_FAIL_NULL(p_script->implicit_initializer); - p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); + if (likely(valid)) { + p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + } } GDScriptInstance *GDScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { @@ -662,7 +666,7 @@ String GDScript::_get_debug_path() const { } Error GDScript::_static_init() { - if (static_initializer) { + if (likely(valid) && static_initializer) { Callable::CallError call_err; static_initializer->call(nullptr, nullptr, 0, call_err); if (call_err.error != Callable::CallError::CALL_OK) { @@ -679,8 +683,6 @@ Error GDScript::_static_init() { return err; } -#ifdef TOOLS_ENABLED - void GDScript::_static_default_init() { for (const KeyValue<StringName, MemberInfo> &E : static_variables_indices) { const GDScriptDataType &type = E.value.data_type; @@ -702,6 +704,8 @@ void GDScript::_static_default_init() { } } +#ifdef TOOLS_ENABLED + void GDScript::_save_old_static_data() { old_static_variables_indices = static_variables_indices; old_static_variables = static_variables; @@ -873,9 +877,6 @@ Error GDScript::reload(bool p_keep_state) { #ifdef TOOLS_ENABLED if (can_run && p_keep_state) { _restore_old_static_data(); - } else if (!can_run) { - // Initialize static variables with sane default values even if the constructor isn't called. - _static_default_init(); } #endif @@ -912,18 +913,15 @@ void GDScript::unload_static() const { } Variant GDScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(!valid)) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } - GDScript *top = this; while (top) { - HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method); - if (E) { - ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); + if (likely(top->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = top->member_functions.find(p_method); + if (E) { + ERR_FAIL_COND_V_MSG(!E->value->is_static(), Variant(), "Can't call non-static function '" + String(p_method) + "' in script."); - return E->value->call(nullptr, p_args, p_argcount, r_error); + return E->value->call(nullptr, p_args, p_argcount, r_error); + } } top = top->_base; } @@ -939,10 +937,6 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { return true; } - if (unlikely(!valid)) { - return false; - } - const GDScript *top = this; while (top) { { @@ -956,7 +950,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(top->valid) && E->value.getter) { Callable::CallError ce; r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); return true; @@ -966,7 +960,7 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(top->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = top->member_functions.find(p_name); if (E && E->value->is_static()) { if (top->rpc_config.has(p_name)) { @@ -999,10 +993,6 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { return true; } - if (unlikely(!valid)) { - return false; - } - GDScript *top = this; while (top) { HashMap<StringName, MemberInfo>::ConstIterator E = top->static_variables_indices.find(p_name); @@ -1017,7 +1007,7 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(top->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1037,10 +1027,6 @@ bool GDScript::_set(const StringName &p_name, const Variant &p_value) { void GDScript::_get_property_list(List<PropertyInfo> *p_properties) const { p_properties->push_back(PropertyInfo(Variant::STRING, "script/source", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); - if (unlikely(!valid)) { - return; - } - List<const GDScript *> classes; const GDScript *top = this; while (top) { @@ -1657,10 +1643,6 @@ GDScript::~GDScript() { ////////////////////////////// bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { - if (unlikely(!script->valid)) { - return false; - } - { HashMap<StringName, GDScript::MemberInfo>::Iterator E = script->member_indices.find(p_name); if (E) { @@ -1674,7 +1656,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(script->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1701,7 +1683,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { return false; } } - if (member->setter) { + if (likely(sptr->valid) && member->setter) { const Variant *args = &value; Callable::CallError err; callp(member->setter, &args, 1, err); @@ -1713,7 +1695,7 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._set); if (E) { Variant name = p_name; @@ -1734,14 +1716,10 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) { } bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { - if (unlikely(!script->valid)) { - return false; - } - { HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = script->member_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(script->valid) && E->value.getter) { Callable::CallError err; r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); if (err.error == Callable::CallError::CALL_OK) { @@ -1766,7 +1744,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { { HashMap<StringName, GDScript::MemberInfo>::ConstIterator E = sptr->static_variables_indices.find(p_name); if (E) { - if (E->value.getter) { + if (likely(sptr->valid) && E->value.getter) { Callable::CallError ce; r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); return true; @@ -1784,7 +1762,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(p_name); if (E) { if (sptr->rpc_config.has(p_name)) { @@ -1804,7 +1782,7 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { } } - { + if (likely(sptr->valid)) { HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get); if (E) { Variant name = p_name; @@ -1844,13 +1822,15 @@ void GDScriptInstance::validate_property(PropertyInfo &p_property) const { const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK) { - p_property = PropertyInfo::from_dict(property); - return; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._validate_property); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK) { + p_property = PropertyInfo::from_dict(property); + return; + } } } sptr = sptr->_base; @@ -1858,49 +1838,47 @@ void GDScriptInstance::validate_property(PropertyInfo &p_property) const { } void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const { - if (unlikely(!script->valid)) { - return; - } - // exported members, not done yet! const GDScript *sptr = script.ptr(); List<PropertyInfo> props; while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); - if (E) { - Callable::CallError err; - Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); - if (err.error == Callable::CallError::CALL_OK) { - ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); - - Array arr = ret; - for (int i = 0; i < arr.size(); i++) { - Dictionary d = arr[i]; - ERR_CONTINUE(!d.has("name")); - ERR_CONTINUE(!d.has("type")); - - PropertyInfo pinfo; - pinfo.name = d["name"]; - pinfo.type = Variant::Type(d["type"].operator int()); - if (d.has("hint")) { - pinfo.hint = PropertyHint(d["hint"].operator int()); - } - if (d.has("hint_string")) { - pinfo.hint_string = d["hint_string"]; - } - if (d.has("usage")) { - pinfo.usage = d["usage"]; - } - if (d.has("class_name")) { - pinfo.class_name = d["class_name"]; - } + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._get_property_list); + if (E) { + Callable::CallError err; + Variant ret = const_cast<GDScriptFunction *>(E->value)->call(const_cast<GDScriptInstance *>(this), nullptr, 0, err); + if (err.error == Callable::CallError::CALL_OK) { + ERR_FAIL_COND_MSG(ret.get_type() != Variant::ARRAY, "Wrong type for _get_property_list, must be an array of dictionaries."); + + Array arr = ret; + for (int i = 0; i < arr.size(); i++) { + Dictionary d = arr[i]; + ERR_CONTINUE(!d.has("name")); + ERR_CONTINUE(!d.has("type")); + + PropertyInfo pinfo; + pinfo.name = d["name"]; + pinfo.type = Variant::Type(d["type"].operator int()); + if (d.has("hint")) { + pinfo.hint = PropertyHint(d["hint"].operator int()); + } + if (d.has("hint_string")) { + pinfo.hint_string = d["hint_string"]; + } + if (d.has("usage")) { + pinfo.usage = d["usage"]; + } + if (d.has("class_name")) { + pinfo.class_name = d["class_name"]; + } - ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); - ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); + ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); + ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); - props.push_back(pinfo); + props.push_back(pinfo); + } } } } @@ -1940,21 +1918,19 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } bool GDScriptInstance::property_can_revert(const StringName &p_name) const { - if (unlikely(!script->valid)) { - return false; - } - Variant name = p_name; const Variant *args[1] = { &name }; const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { - return true; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { + return true; + } } } sptr = sptr->_base; @@ -1964,22 +1940,20 @@ bool GDScriptInstance::property_can_revert(const StringName &p_name) const { } bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { - if (unlikely(!script->valid)) { - return false; - } - Variant name = p_name; const Variant *args[1] = { &name }; const GDScript *sptr = script.ptr(); while (sptr) { - HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); - if (E) { - Callable::CallError err; - Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); - if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { - r_ret = ret; - return true; + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + r_ret = ret; + return true; + } } } sptr = sptr->_base; @@ -2035,30 +2009,28 @@ void GDScriptInstance::_call_implicit_ready_recursively(GDScript *p_script) { if (p_script->_base) { _call_implicit_ready_recursively(p_script->_base); } - if (p_script->implicit_ready) { + if (likely(p_script->valid) && p_script->implicit_ready) { Callable::CallError err; p_script->implicit_ready->call(this, nullptr, 0, err); } } Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(!script->valid)) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } - GDScript *sptr = script.ptr(); if (unlikely(p_method == SceneStringName(_ready))) { // Call implicit ready first, including for the super classes recursively. _call_implicit_ready_recursively(sptr); } while (sptr) { - HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); - if (E) { - return E->value->call(this, p_args, p_argcount, r_error); + if (likely(sptr->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); + if (E) { + return E->value->call(this, p_args, p_argcount, r_error); + } } sptr = sptr->_base; } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } @@ -2083,12 +2055,14 @@ void GDScriptInstance::notification(int p_notification, bool p_reversed) { sptr = sptr->_base; } for (GDScript *sc : pl) { - HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); - if (E) { - Callable::CallError err; - E->value->call(this, args, 1, err); - if (err.error != Callable::CallError::CALL_OK) { - //print error about notification call + if (likely(sc->valid)) { + HashMap<StringName, GDScriptFunction *>::Iterator E = sc->member_functions.find(GDScriptLanguage::get_singleton()->strings._notification); + if (E) { + Callable::CallError err; + E->value->call(this, args, 1, err); + if (err.error != Callable::CallError::CALL_OK) { + //print error about notification call + } } } } @@ -2759,7 +2733,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b String source = f->get_as_utf8_string(); GDScriptParser parser; - err = parser.parse(source, p_path, false); + err = parser.parse(source, p_path, false, false); const GDScriptParser::ClassNode *c = parser.get_tree(); if (!c) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 728459de44..d097cb193b 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -169,9 +169,7 @@ private: GDScriptFunction *static_initializer = nullptr; Error _static_init(); -#ifdef TOOLS_ENABLED void _static_default_init(); // Initialize static variables with default values based on their types. -#endif int subclass_count = 0; RBSet<Object *> instances; diff --git a/modules/gdscript/gdscript_cache.cpp b/modules/gdscript/gdscript_cache.cpp index ac6f5f05c6..c56ae0fb14 100644 --- a/modules/gdscript/gdscript_cache.cpp +++ b/modules/gdscript/gdscript_cache.cpp @@ -91,12 +91,8 @@ Error GDScriptParserRef::raise_status(Status p_new_status) { result = get_analyzer()->resolve_interface(); } break; case INTERFACE_SOLVED: { - status = BODY_SOLVED; - result = get_analyzer()->resolve_body(); - } break; - case BODY_SOLVED: { status = FULLY_SOLVED; - result = get_analyzer()->resolve_dependencies(); + result = get_analyzer()->resolve_body(); } break; case FULLY_SOLVED: { return result; @@ -344,7 +340,11 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro } } + // Allowing lifting the lock might cause a script to be reloaded multiple times, + // which, as a last resort deadlock prevention strategy, is a good tradeoff. + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&singleton->mutex); r_error = script->reload(true); + WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); if (r_error) { return script; } diff --git a/modules/gdscript/gdscript_cache.h b/modules/gdscript/gdscript_cache.h index c738233beb..16121cc082 100644 --- a/modules/gdscript/gdscript_cache.h +++ b/modules/gdscript/gdscript_cache.h @@ -48,7 +48,6 @@ public: PARSED, INHERITANCE_SOLVED, INTERFACE_SOLVED, - BODY_SOLVED, FULLY_SOLVED, }; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index eeffc13a10..5469dad3f7 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -3006,6 +3006,8 @@ Error GDScriptCompiler::_compile_class(GDScript *p_script, const GDScriptParser: has_static_data = has_static_data || inner_class->has_static_data; } + p_script->_static_default_init(); + p_script->valid = true; return OK; } @@ -3228,11 +3230,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri GDScriptCache::add_static_script(p_script); } - err = GDScriptCache::finish_compiling(main_script->path); - if (err) { - main_script->valid = false; - } - return err; + return GDScriptCache::finish_compiling(main_script->path); } String GDScriptCompiler::get_error() const { diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index aaa09134f0..a1ea94667d 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -309,13 +309,14 @@ void GDScriptParser::set_last_completion_call_arg(int p_argument) { completion_call_stack.back()->get().argument = p_argument; } -Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion) { +Error GDScriptParser::parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body) { clear(); String source = p_source_code; int cursor_line = -1; int cursor_column = -1; for_completion = p_for_completion; + parse_body = p_parse_body; int tab_size = 4; #ifdef TOOLS_ENABLED @@ -689,6 +690,12 @@ void GDScriptParser::parse_program() { } } + // When the only thing needed is the class name and the icon, we don't need to parse the hole file. + // It really speed up the call to GDScriptLanguage::get_global_class_name especially for large script. + if (!parse_body) { + return; + } + #undef PUSH_PENDING_ANNOTATIONS_TO_HEAD parse_class_body(true); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 96358165c0..21942222cf 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -1329,6 +1329,7 @@ private: bool _is_tool = false; String script_path; bool for_completion = false; + bool parse_body = true; bool panic_mode = false; bool can_break = false; bool can_continue = false; @@ -1560,7 +1561,7 @@ private: #endif // TOOLS_ENABLED public: - Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion); + Error parse(const String &p_source_code, const String &p_script_path, bool p_for_completion, bool p_parse_body = true); Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path); ClassNode *get_tree() const { return head; } bool is_tool() const { return _is_tool; } diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 0ad84240e4..8e1abba3bb 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -142,8 +142,9 @@ def detect_build_env_arch(): if os.getenv("VCTOOLSINSTALLDIR"): host_path_index = os.getenv("PATH").upper().find(os.getenv("VCTOOLSINSTALLDIR").upper() + "BIN\\HOST") if host_path_index > -1: - first_path_arch = os.getenv("PATH").split(";")[0].rsplit("\\", 1)[-1].lower() - return msvc_target_aliases[first_path_arch] + first_path_arch = os.getenv("PATH")[host_path_index:].split(";")[0].rsplit("\\", 1)[-1].lower() + if first_path_arch in msvc_target_aliases.keys(): + return msvc_target_aliases[first_path_arch] msys_target_aliases = { "mingw32": "x86_32", diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index b2567e431b..7d121c9d87 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -324,6 +324,21 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { is_uniform_type_compatible = E->get().type == cached.get_type(); } +#ifndef DISABLE_DEPRECATED + // PackedFloat32Array -> PackedVector4Array conversion. + if (!is_uniform_type_compatible && E->get().type == Variant::PACKED_VECTOR4_ARRAY && cached.get_type() == Variant::PACKED_FLOAT32_ARRAY) { + PackedVector4Array varray; + PackedFloat32Array array = (PackedFloat32Array)cached; + + for (int i = 0; i + 3 < array.size(); i += 4) { + varray.push_back(Vector4(array[i], array[i + 1], array[i + 2], array[i + 3])); + } + + param_cache.insert(E->get().name, varray); + is_uniform_type_compatible = true; + } +#endif + if (is_uniform_type_compatible && E->get().type == Variant::OBJECT && cached.get_type() == Variant::OBJECT) { // Check if the Object class (hint string) changed, for example Texture2D sampler to Texture3D. // Allow inheritance, Texture2D type sampler should also accept CompressedTexture2D. diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 1e9690a8ae..5b4931edec 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -3982,12 +3982,9 @@ Variant ShaderLanguage::constant_value_to_variant(const Vector<ShaderLanguage::C } value = Variant(array); } else { - PackedFloat32Array array; + PackedVector4Array array; for (int i = 0; i < array_size; i += 4) { - array.push_back(p_value[i].real); - array.push_back(p_value[i + 1].real); - array.push_back(p_value[i + 2].real); - array.push_back(p_value[i + 3].real); + array.push_back(Vector4(p_value[i].real, p_value[i + 1].real, p_value[i + 2].real, p_value[i + 3].real)); } value = Variant(array); } @@ -4219,7 +4216,7 @@ PropertyInfo ShaderLanguage::uniform_to_property_info(const ShaderNode::Uniform if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) { pi.type = Variant::PACKED_COLOR_ARRAY; } else { - pi.type = Variant::PACKED_FLOAT32_ARRAY; + pi.type = Variant::PACKED_VECTOR4_ARRAY; } } else { if (p_uniform.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SOURCE_COLOR) { |
