Merge pull request #92303 from Hilderin/fix-synchronization-global-class-name

Fix synchronization of global class name
This commit is contained in:
Rémi Verschelde 2024-06-28 14:42:37 +02:00
commit d4b7ede882
No known key found for this signature in database
GPG Key ID: C3336907360768E1
13 changed files with 455 additions and 201 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
for (List<String>::Element *E = files.front(); E; E = E->next(), idx++) {
String ext = E->get().get_extension().to_lower();
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 (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() {
update_script_mutex.lock();
for (const String &path : update_script_paths) {
EditorFileSystem::get_singleton()->register_global_class_script(path, path);
if (update_script_paths.is_empty()) {
return;
}
// Parse documentation second, as it requires the class names to be correct and registered
for (const String &path : update_script_paths) {
update_script_mutex.lock();
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);
}
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"));

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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; }

View File

@ -2759,7 +2759,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) {

View File

@ -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);

View File

@ -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; }