summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/io/resource_loader.cpp37
-rw-r--r--core/io/resource_loader.h3
-rw-r--r--core/object/worker_thread_pool.cpp91
-rw-r--r--core/object/worker_thread_pool.h24
-rw-r--r--core/templates/command_queue_mt.h4
-rw-r--r--doc/classes/ResourceLoader.xml3
-rw-r--r--editor/animation_track_editor.cpp12
-rw-r--r--editor/editor_autoload_settings.cpp57
-rw-r--r--editor/editor_autoload_settings.h1
-rw-r--r--editor/editor_file_system.cpp479
-rw-r--r--editor/editor_file_system.h45
-rw-r--r--editor/editor_node.cpp36
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/plugins/editor_plugin.cpp10
-rw-r--r--editor/project_settings_editor.cpp4
-rw-r--r--editor/project_settings_editor.h1
-rw-r--r--editor/scene_tree_dock.cpp9
-rw-r--r--modules/gdscript/gdscript.cpp226
-rw-r--r--modules/gdscript/gdscript.h2
-rw-r--r--modules/gdscript/gdscript_cache.cpp10
-rw-r--r--modules/gdscript/gdscript_cache.h1
-rw-r--r--modules/gdscript/gdscript_compiler.cpp8
-rw-r--r--modules/gdscript/gdscript_parser.cpp9
-rw-r--r--modules/gdscript/gdscript_parser.h3
-rw-r--r--platform/windows/detect.py5
-rw-r--r--scene/resources/material.cpp15
-rw-r--r--servers/rendering/shader_language.cpp9
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 = &pr;
- 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 = &pr;
- 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) {