Browse Source

Fix synchronization of global class name

Hilderin 1 year ago
parent
commit
39369db029

+ 31 - 26
editor/editor_autoload_settings.cpp

@@ -55,11 +55,6 @@ void EditorAutoloadSettings::_notification(int p_what) {
 				file_dialog->add_filter("*." + E);
 				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")));
 			browse_button->set_icon(get_editor_theme_icon(SNAME("Folder")));
 		} break;
 		} break;
 
 
@@ -419,6 +414,8 @@ Node *EditorAutoloadSettings::_create_autoload(const String &p_path) {
 
 
 		Ref<Script> scr = res;
 		Ref<Script> scr = res;
 		if (scr.is_valid()) {
 		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();
 			StringName ibt = scr->get_instance_base_type();
 			bool valid_type = ClassDB::is_parent_class(ibt, "Node");
 			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));
 			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;
 	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() {
 void EditorAutoloadSettings::update_autoload() {
 	if (updating_autoload) {
 	if (updating_autoload) {
 		return;
 		return;
@@ -857,27 +883,6 @@ EditorAutoloadSettings::EditorAutoloadSettings() {
 		autoload_cache.push_back(info);
 		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);
 	HBoxContainer *hbc = memnew(HBoxContainer);
 	add_child(hbc);
 	add_child(hbc);
 
 

+ 1 - 0
editor/editor_autoload_settings.h

@@ -104,6 +104,7 @@ protected:
 	static void _bind_methods();
 	static void _bind_methods();
 
 
 public:
 public:
+	void init_autoloads();
 	void update_autoload();
 	void update_autoload();
 	bool autoload_add(const String &p_name, const String &p_path);
 	bool autoload_add(const String &p_name, const String &p_path);
 	void autoload_remove(const String &p_name);
 	void autoload_remove(const String &p_name);

+ 339 - 140
editor/editor_file_system.cpp

@@ -44,6 +44,7 @@
 #include "editor/editor_paths.h"
 #include "editor/editor_paths.h"
 #include "editor/editor_resource_preview.h"
 #include "editor/editor_resource_preview.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_settings.h"
+#include "editor/project_settings_editor.h"
 #include "scene/resources/packed_scene.h"
 #include "scene/resources/packed_scene.h"
 
 
 EditorFileSystem *EditorFileSystem::singleton = nullptr;
 EditorFileSystem *EditorFileSystem::singleton = nullptr;
@@ -206,17 +207,68 @@ EditorFileSystemDirectory::EditorFileSystemDirectory() {
 }
 }
 
 
 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() {
 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
 	//read .fscache
 	String cpath;
 	String cpath;
@@ -318,23 +370,33 @@ void EditorFileSystem::_scan_filesystem() {
 	}
 	}
 
 
 	EditorProgressBG scan_progress("efs", "ScanFS", 1000);
 	EditorProgressBG scan_progress("efs", "ScanFS", 1000);
-
 	ScanProgress sp;
 	ScanProgress sp;
-	sp.low = 0;
-	sp.hi = 1;
+	sp.hi = nb_files_total;
 	sp.progress = &scan_progress;
 	sp.progress = &scan_progress;
 
 
 	new_filesystem = memnew(EditorFileSystemDirectory);
 	new_filesystem = memnew(EditorFileSystemDirectory);
 	new_filesystem->parent = nullptr;
 	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
 	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
 		//on the first scan this is done from the main thread after re-importing
 		_save_filesystem_cache();
 		_save_filesystem_cache();
 	}
 	}
@@ -567,6 +629,10 @@ bool EditorFileSystem::_scan_import_support(const Vector<String> &reimports) {
 bool EditorFileSystem::_update_scan_actions() {
 bool EditorFileSystem::_update_scan_actions() {
 	sources_changed.clear();
 	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;
 	bool fs_changed = false;
 
 
 	Vector<String> reimports;
 	Vector<String> reimports;
@@ -615,7 +681,7 @@ bool EditorFileSystem::_update_scan_actions() {
 				fs_changed = true;
 				fs_changed = true;
 
 
 				if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) {
 				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")) {
 				if (ia.new_file->type == SNAME("PackedScene")) {
 					_queue_update_scene_groups(ia.dir->get_file_path(idx));
 					_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);
 				int idx = ia.dir->find_file_index(ia.file);
 				ERR_CONTINUE(idx == -1);
 				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"))) {
 				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")) {
 				if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
 					_queue_update_scene_groups(ia.dir->get_file_path(idx));
 					_queue_update_scene_groups(ia.dir->get_file_path(idx));
@@ -637,6 +704,15 @@ bool EditorFileSystem::_update_scan_actions() {
 				memdelete(ia.dir->files[idx]);
 				memdelete(ia.dir->files[idx]);
 				ia.dir->files.remove_at(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;
 				fs_changed = true;
 
 
 			} break;
 			} break;
@@ -667,10 +743,11 @@ bool EditorFileSystem::_update_scan_actions() {
 				ERR_CONTINUE(idx == -1);
 				ERR_CONTINUE(idx == -1);
 				String full_path = ia.dir->get_file_path(idx);
 				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);
 					_queue_update_scene_groups(full_path);
 				}
 				}
 
 
@@ -711,6 +788,10 @@ bool EditorFileSystem::_update_scan_actions() {
 		_save_filesystem_cache();
 		_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()) {
 	if (reloads.size()) {
 		emit_signal(SNAME("resources_reload"), reloads);
 		emit_signal(SNAME("resources_reload"), reloads);
 	}
 	}
@@ -728,6 +809,14 @@ void EditorFileSystem::scan() {
 		return;
 		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();
 	_update_extensions();
 
 
 	if (!use_threads) {
 	if (!use_threads) {
@@ -741,14 +830,14 @@ void EditorFileSystem::scan() {
 		filesystem = new_filesystem;
 		filesystem = new_filesystem;
 		new_filesystem = nullptr;
 		new_filesystem = nullptr;
 		_update_scan_actions();
 		_update_scan_actions();
-		scanning = false;
-		_update_pending_script_classes();
-		_update_pending_scene_groups();
 		// Update all icons so they are loaded for the FileSystemDock.
 		// Update all icons so they are loaded for the FileSystemDock.
 		_update_files_icon_path();
 		_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("filesystem_changed"));
 		emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
 		emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
-		first_scan = false;
 	} else {
 	} else {
 		ERR_FAIL_COND(thread.is_started());
 		ERR_FAIL_COND(thread.is_started());
 		set_process(true);
 		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::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> dirs;
 	List<String> files;
 	List<String> files;
 
 
 	String cd = da->get_current_dir();
 	String cd = da->get_current_dir();
 
 
-	p_dir->modified_time = FileAccess::get_modified_time(cd);
-
 	da->list_dir_begin();
 	da->list_dir_begin();
 	while (true) {
 	while (true) {
 		String f = da->get_next();
 		String f = da->get_next();
@@ -816,55 +896,59 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 	dirs.sort_custom<FileNoCaseComparator>();
 	dirs.sort_custom<FileNoCaseComparator>();
 	files.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) {
 		if (da->change_dir(E->get()) == OK) {
 			String d = da->get_current_dir();
 			String d = da->get_current_dir();
 
 
 			if (d == cd || !d.begins_with(cd)) {
 			if (d == cd || !d.begins_with(cd)) {
 				da->change_dir(cd); //avoid recursion
 				da->change_dir(cd); //avoid recursion
 			} else {
 			} 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("..");
 				da->change_dir("..");
 			}
 			}
 		} else {
 		} else {
 			ERR_PRINT("Cannot go into subdir '" + E->get() + "'.");
 			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)) {
 		if (!valid_extensions.has(ext)) {
+			p_progress.increment();
 			continue; //invalid
 			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);
 		FileCache *fc = file_cache.getptr(path);
 		uint64_t mt = FileAccess::get_modified_time(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;
 					ItemAction ia;
 					ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
 					ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
 					ia.dir = p_dir;
 					ia.dir = p_dir;
-					ia.file = E->get();
+					ia.file = fi->file;
 					scan_actions.push_back(ia);
 					scan_actions.push_back(ia);
 				}
 				}
 
 
@@ -923,7 +1007,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 				ItemAction ia;
 				ItemAction ia;
 				ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
 				ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
 				ia.dir = p_dir;
 				ia.dir = p_dir;
-				ia.file = E->get();
+				ia.file = fi->file;
 				scan_actions.push_back(ia);
 				scan_actions.push_back(ia);
 			}
 			}
 		} else {
 		} 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_name = fc->script_class_name;
 				fi->script_class_extends = fc->script_class_extends;
 				fi->script_class_extends = fc->script_class_extends;
 				fi->script_class_icon_path = fc->script_class_icon_path;
 				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 {
 			} else {
 				//new or modified time
 				//new or modified time
 				fi->type = ResourceLoader::get_resource_type(path);
 				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.
 				// 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 (!dep_update_list.has(path)) {
 					if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
 					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")) {
 					if (fi->type == SNAME("PackedScene")) {
 						_queue_update_scene_groups(path);
 						_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());
 	uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path());
 
 
 	bool updated_dir = false;
 	bool updated_dir = false;
 	String cd = p_dir->get_path();
 	String cd = p_dir->get_path();
+	int diff_nb_files = 0;
 
 
 	if (current_mtime != p_dir->modified_time || using_fat32_or_exfat) {
 	if (current_mtime != p_dir->modified_time || using_fat32_or_exfat) {
 		updated_dir = true;
 		updated_dir = true;
@@ -999,6 +1098,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
 			p_dir->get_subdir(i)->verified = false;
 			p_dir->get_subdir(i)->verified = false;
 		}
 		}
 
 
+		diff_nb_files -= p_dir->files.size();
+
 		//then scan files and directories and check what's different
 		//then scan files and directories and check what's different
 
 
 		Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 		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);
 				int idx = p_dir->find_dir_index(f);
 				if (idx == -1) {
 				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;
 						continue;
 					}
 					}
 
 
-					EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
+					ScannedDirectory sd;
+					sd.name = f;
+					sd.full_path = dir_path;
 
 
+					EditorFileSystemDirectory *efd = memnew(EditorFileSystemDirectory);
 					efd->parent = p_dir;
 					efd->parent = p_dir;
 					efd->name = f;
 					efd->name = f;
+
 					Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES);
 					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;
 					ItemAction ia;
 					ia.action = ItemAction::ACTION_DIR_ADD;
 					ia.action = ItemAction::ACTION_DIR_ADD;
@@ -1088,7 +1197,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
 						ia.file = f;
 						ia.file = f;
 						scan_actions.push_back(ia);
 						scan_actions.push_back(ia);
 					}
 					}
-
+					diff_nb_files++;
 				} else {
 				} else {
 					p_dir->files[idx]->verified = true;
 					p_dir->files[idx]->verified = true;
 				}
 				}
@@ -1106,6 +1215,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
 			ia.dir = p_dir;
 			ia.dir = p_dir;
 			ia.file = p_dir->files[i]->file;
 			ia.file = p_dir->files[i]->file;
 			scan_actions.push_back(ia);
 			scan_actions.push_back(ia);
+			diff_nb_files--;
 			continue;
 			continue;
 		}
 		}
 
 
@@ -1152,10 +1262,16 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
 				scan_actions.push_back(ia);
 				scan_actions.push_back(ia);
 			}
 			}
 		}
 		}
+
+		p_progress.increment();
 	}
 	}
 
 
 	for (int i = 0; i < p_dir->subdirs.size(); i++) {
 	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())) {
 		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
 			//this directory was removed or ignored, add action to remove it
 			ItemAction ia;
 			ItemAction ia;
 			ia.action = ItemAction::ACTION_DIR_REMOVE;
 			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);
 		_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) {
 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) {
 void EditorFileSystem::_thread_func_sources(void *_userdata) {
 	EditorFileSystem *efs = (EditorFileSystem *)_userdata;
 	EditorFileSystem *efs = (EditorFileSystem *)_userdata;
 	if (efs->filesystem) {
 	if (efs->filesystem) {
 		EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
 		EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
 		ScanProgress sp;
 		ScanProgress sp;
 		sp.progress = &pr;
 		sp.progress = &pr;
-		sp.hi = 1;
-		sp.low = 0;
+		sp.hi = efs->nb_files_total;
 		efs->_scan_fs_changes(efs->filesystem, sp);
 		efs->_scan_fs_changes(efs->filesystem, sp);
 	}
 	}
 	efs->scanning_changes_done.set();
 	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() {
 void EditorFileSystem::scan_changes() {
 	if (first_scan || // Prevent a premature changes scan from inhibiting the first full scan
 	if (first_scan || // Prevent a premature changes scan from inhibiting the first full scan
 			scanning || scanning_changes || thread.is_started()) {
 			scanning || scanning_changes || thread.is_started()) {
@@ -1210,14 +1373,10 @@ void EditorFileSystem::scan_changes() {
 			EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
 			EditorProgressBG pr("sources", TTR("ScanSources"), 1000);
 			ScanProgress sp;
 			ScanProgress sp;
 			sp.progress = &pr;
 			sp.progress = &pr;
-			sp.hi = 1;
-			sp.low = 0;
+			sp.hi = nb_files_total;
 			scan_total = 0;
 			scan_total = 0;
 			_scan_fs_changes(filesystem, sp);
 			_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"));
 				emit_signal(SNAME("filesystem_changed"));
 			}
 			}
 		}
 		}
@@ -1282,13 +1441,13 @@ void EditorFileSystem::_notification(int p_what) {
 							thread_sources.wait_to_finish();
 							thread_sources.wait_to_finish();
 						}
 						}
 						bool changed = _update_scan_actions();
 						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) {
 						if (changed) {
 							emit_signal(SNAME("filesystem_changed"));
 							emit_signal(SNAME("filesystem_changed"));
 						}
 						}
 						emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
 						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.
 						scanning_changes = false; // Changed to false here to prevent recursive triggering of scan thread.
 						done_importing = true;
 						done_importing = true;
 					}
 					}
@@ -1302,13 +1461,13 @@ void EditorFileSystem::_notification(int p_what) {
 					new_filesystem = nullptr;
 					new_filesystem = nullptr;
 					thread.wait_to_finish();
 					thread.wait_to_finish();
 					_update_scan_actions();
 					_update_scan_actions();
-					_update_pending_script_classes();
-					_update_pending_scene_groups();
 					// Update all icons so they are loaded for the FileSystemDock.
 					// Update all icons so they are loaded for the FileSystemDock.
 					_update_files_icon_path();
 					_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("filesystem_changed"));
 					emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
 					emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
-					first_scan = false;
 				}
 				}
 
 
 				if (done_importing && scan_changes_pending) {
 				if (done_importing && scan_changes_pending) {
@@ -1323,7 +1482,7 @@ void EditorFileSystem::_notification(int p_what) {
 }
 }
 
 
 bool EditorFileSystem::is_scanning() const {
 bool EditorFileSystem::is_scanning() const {
-	return scanning || scanning_changes;
+	return scanning || scanning_changes || first_scan;
 }
 }
 
 
 float EditorFileSystem::get_scanning_progress() const {
 float EditorFileSystem::get_scanning_progress() const {
@@ -1624,14 +1783,41 @@ void EditorFileSystem::_update_files_icon_path(EditorFileSystemDirectory *edp) {
 }
 }
 
 
 void EditorFileSystem::_update_script_classes() {
 void EditorFileSystem::_update_script_classes() {
+	if (update_script_paths.is_empty()) {
+		return;
+	}
+
 	update_script_mutex.lock();
 	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;
 		int index = -1;
 		EditorFileSystemDirectory *efd = find_file(path, &index);
 		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();
 	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_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();
 	update_script_mutex.unlock();
 }
 }
 
 
 void EditorFileSystem::_update_scene_groups() {
 void EditorFileSystem::_update_scene_groups() {
+	if (update_scene_paths.is_empty()) {
+		return;
+	}
+
 	EditorProgress *ep = nullptr;
 	EditorProgress *ep = nullptr;
 	if (update_scene_paths.size() > 1) {
 	if (update_scene_paths.size() > 1) {
 		ep = memnew(EditorProgress("update_scene_groups", TTR("Update Scene Groups"), update_scene_paths.size()));
 		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"))) {
 				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()) {
 					if (!fs->files[cpos]->script_class_icon_path.is_empty()) {
 						update_files_icon_cache = true;
 						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_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]->type = type;
 			fs->files[cpos]->resource_script_class = script_class;
 			fs->files[cpos]->resource_script_class = script_class;
 			fs->files[cpos]->uid = uid;
 			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);
 			EditorResourcePreview::get_singleton()->check_for_invalidation(file);
 
 
 			if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
 			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")) {
 			if (fs->files[cpos]->type == SNAME("PackedScene")) {
 				_queue_update_scene_groups(file);
 				_queue_update_scene_groups(file);
 			}
 			}
+
 			if (fs->files[cpos]->type == SNAME("Resource")) {
 			if (fs->files[cpos]->type == SNAME("Resource")) {
 				files_to_update_icon_path.push_back(fs->files[cpos]);
 				files_to_update_icon_path.push_back(fs->files[cpos]);
 			} else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) {
 			} else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) {
 				update_files_icon_cache = true;
 				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;
 			updated = true;
 		}
 		}
 	}
 	}
 
 
 	if (updated) {
 	if (updated) {
-		_update_pending_script_classes();
-		_update_pending_scene_groups();
+		_process_update_pending();
 		if (update_files_icon_cache) {
 		if (update_files_icon_cache) {
 			_update_files_icon_path();
 			_update_files_icon_path();
 		} else {
 		} else {
@@ -1894,31 +2088,37 @@ HashSet<String> EditorFileSystem::get_valid_extensions() const {
 	return valid_extensions;
 	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
 	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;
 		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.
 	ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
 
 
 	_save_filesystem_cache();
 	_save_filesystem_cache();
-	_update_pending_script_classes();
-	_update_pending_scene_groups();
+	_process_update_pending();
 	importing = false;
 	importing = false;
 	if (!is_scanning()) {
 	if (!is_scanning()) {
 		emit_signal(SNAME("filesystem_changed"));
 		emit_signal(SNAME("filesystem_changed"));

+ 36 - 9
editor/editor_file_system.h

@@ -159,11 +159,21 @@ class EditorFileSystem : public Node {
 		EditorFileSystemDirectory::FileInfo *new_file = nullptr;
 		EditorFileSystemDirectory::FileInfo *new_file = nullptr;
 	};
 	};
 
 
+	struct ScannedDirectory {
+		String name;
+		String full_path;
+		Vector<ScannedDirectory *> subdirs;
+		List<String> files;
+
+		~ScannedDirectory();
+	};
+
 	bool use_threads = false;
 	bool use_threads = false;
 	Thread thread;
 	Thread thread;
 	static void _thread_func(void *_userdata);
 	static void _thread_func(void *_userdata);
 
 
 	EditorFileSystemDirectory *new_filesystem = nullptr;
 	EditorFileSystemDirectory *new_filesystem = nullptr;
+	ScannedDirectory *first_scan_root_dir = nullptr;
 
 
 	bool scanning = false;
 	bool scanning = false;
 	bool importing = false;
 	bool importing = false;
@@ -172,8 +182,11 @@ class EditorFileSystem : public Node {
 	float scan_total;
 	float scan_total;
 	String filesystem_settings_version_for_import;
 	String filesystem_settings_version_for_import;
 	bool revalidate_import_files = false;
 	bool revalidate_import_files = false;
+	int nb_files_total = 0;
 
 
 	void _scan_filesystem();
 	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;
 	HashSet<String> late_update_files;
 
 
@@ -202,11 +215,10 @@ class EditorFileSystem : public Node {
 	HashSet<String> dep_update_list;
 	HashSet<String> dep_update_list;
 
 
 	struct ScanProgress {
 	struct ScanProgress {
-		float low = 0;
 		float hi = 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();
 	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;
 	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);
 	void _delete_internal_files(const String &p_file);
+	int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir);
 
 
 	HashSet<String> textfile_extensions;
 	HashSet<String> textfile_extensions;
 	HashSet<String> valid_extensions;
 	HashSet<String> valid_extensions;
 	HashSet<String> import_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;
 	Thread thread_sources;
 	bool scanning_changes = false;
 	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;
 	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_script_classes();
-	void _update_pending_script_classes();
+	void _update_script_documentation();
+	void _process_update_pending();
 
 
 	Mutex update_scene_mutex;
 	Mutex update_scene_mutex;
 	HashSet<String> update_scene_paths;
 	HashSet<String> update_scene_paths;
@@ -300,6 +323,10 @@ class EditorFileSystem : public Node {
 
 
 	void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
 	void _update_file_icon_path(EditorFileSystemDirectory::FileInfo *file_info);
 	void _update_files_icon_path(EditorFileSystemDirectory *edp = nullptr);
 	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:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);

+ 19 - 17
editor/editor_node.cpp

@@ -706,23 +706,7 @@ void EditorNode::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_READY: {
 		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_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);
 			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);
 	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) {
 void EditorNode::_on_plugin_ready(Object *p_script, const String &p_activate_name) {
 	Ref<Script> scr = Object::cast_to<Script>(p_script);
 	Ref<Script> scr = Object::cast_to<Script>(p_script);
 	if (scr.is_null()) {
 	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.
 	// FIXME: Move this to a cleaner location, it's hacky to do this in _fs_changed.
 	String export_error;
 	String export_error;
 	Error err = OK;
 	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()) {
 	if (!export_defer.preset.is_empty() && !EditorFileSystem::get_singleton()->is_scanning()) {
 		String preset_name = export_defer.preset;
 		String preset_name = export_defer.preset;
 		// Ensures export_project does not loop infinitely, because notifications may
 		// Ensures export_project does not loop infinitely, because notifications may

+ 1 - 0
editor/editor_node.h

@@ -683,6 +683,7 @@ protected:
 
 
 public:
 public:
 	// Public for use with callable_mp.
 	// Public for use with callable_mp.
+	void init_plugins();
 	void _on_plugin_ready(Object *p_script, const String &p_activate_name);
 	void _on_plugin_ready(Object *p_script, const String &p_activate_name);
 
 
 	void editor_select(int p_which);
 	void editor_select(int p_which);

+ 1 - 1
editor/filesystem_dock.cpp

@@ -546,7 +546,7 @@ void FileSystemDock::_notification(int p_what) {
 
 
 		case NOTIFICATION_PROCESS: {
 		case NOTIFICATION_PROCESS: {
 			if (EditorFileSystem::get_singleton()->is_scanning()) {
 			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;
 		} break;
 
 

+ 8 - 2
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) {
 void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer, bool p_first_priority) {
 	ERR_FAIL_COND(!p_importer.is_valid());
 	ERR_FAIL_COND(!p_importer.is_valid());
 	ResourceFormatImporter::get_singleton()->add_importer(p_importer, p_first_priority);
 	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) {
 void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) {
 	ERR_FAIL_COND(!p_importer.is_valid());
 	ERR_FAIL_COND(!p_importer.is_valid());
 	ResourceFormatImporter::get_singleton()->remove_importer(p_importer);
 	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();
 		callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
 	}
 	}
 }
 }

+ 4 - 0
editor/project_settings_editor.cpp

@@ -91,6 +91,10 @@ void ProjectSettingsEditor::update_plugins() {
 	plugin_settings->update_plugins();
 	plugin_settings->update_plugins();
 }
 }
 
 
+void ProjectSettingsEditor::init_autoloads() {
+	autoload_settings->init_autoloads();
+}
+
 void ProjectSettingsEditor::_setting_edited(const String &p_name) {
 void ProjectSettingsEditor::_setting_edited(const String &p_name) {
 	queue_save();
 	queue_save();
 }
 }

+ 1 - 0
editor/project_settings_editor.h

@@ -123,6 +123,7 @@ public:
 	void set_plugins_page();
 	void set_plugins_page();
 	void set_general_page(const String &p_category);
 	void set_general_page(const String &p_category);
 	void update_plugins();
 	void update_plugins();
+	void init_autoloads();
 
 
 	EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
 	EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
 	GroupSettingsEditor *get_group_settings() { return group_settings; }
 	GroupSettingsEditor *get_group_settings() { return group_settings; }

+ 1 - 1
modules/gdscript/gdscript.cpp

@@ -2759,7 +2759,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b
 	String source = f->get_as_utf8_string();
 	String source = f->get_as_utf8_string();
 
 
 	GDScriptParser parser;
 	GDScriptParser parser;
-	err = parser.parse(source, p_path, false);
+	err = parser.parse(source, p_path, false, false);
 
 
 	const GDScriptParser::ClassNode *c = parser.get_tree();
 	const GDScriptParser::ClassNode *c = parser.get_tree();
 	if (!c) {
 	if (!c) {

+ 8 - 1
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;
 	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();
 	clear();
 
 
 	String source = p_source_code;
 	String source = p_source_code;
 	int cursor_line = -1;
 	int cursor_line = -1;
 	int cursor_column = -1;
 	int cursor_column = -1;
 	for_completion = p_for_completion;
 	for_completion = p_for_completion;
+	parse_body = p_parse_body;
 
 
 	int tab_size = 4;
 	int tab_size = 4;
 #ifdef TOOLS_ENABLED
 #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
 #undef PUSH_PENDING_ANNOTATIONS_TO_HEAD
 
 
 	parse_class_body(true);
 	parse_class_body(true);

+ 2 - 1
modules/gdscript/gdscript_parser.h

@@ -1329,6 +1329,7 @@ private:
 	bool _is_tool = false;
 	bool _is_tool = false;
 	String script_path;
 	String script_path;
 	bool for_completion = false;
 	bool for_completion = false;
+	bool parse_body = true;
 	bool panic_mode = false;
 	bool panic_mode = false;
 	bool can_break = false;
 	bool can_break = false;
 	bool can_continue = false;
 	bool can_continue = false;
@@ -1560,7 +1561,7 @@ private:
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 public:
 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);
 	Error parse_binary(const Vector<uint8_t> &p_binary, const String &p_script_path);
 	ClassNode *get_tree() const { return head; }
 	ClassNode *get_tree() const { return head; }
 	bool is_tool() const { return _is_tool; }
 	bool is_tool() const { return _is_tool; }