diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 8a8c86087d4..a73adf9d747 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -154,6 +154,19 @@ String ResourceUID::get_id_path(ID p_id) const { ERR_FAIL_COND_V_MSG(p_id == INVALID_ID, String(), "Invalid UID."); MutexLock l(mutex); const ResourceUID::Cache *cache = unique_ids.getptr(p_id); + +#if TOOLS_ENABLED + // On startup, the scan_for_uid_on_startup callback should be set and will + // execute EditorFileSystem::scan_for_uid, which scans all project files + // to reload the UID cache before the first scan. + // Note: EditorFileSystem::scan_for_uid sets scan_for_uid_on_startup to nullptr + // once the first scan_for_uid is complete. + if (!cache && scan_for_uid_on_startup) { + scan_for_uid_on_startup(); + cache = unique_ids.getptr(p_id); + } +#endif + ERR_FAIL_COND_V_MSG(!cache, String(), vformat("Unrecognized UID: \"%s\".", id_to_text(p_id))); const CharString &cs = cache->cs; return String::utf8(cs.ptr()); diff --git a/core/io/resource_uid.h b/core/io/resource_uid.h index 6d96953fcc1..2912168dcb4 100644 --- a/core/io/resource_uid.h +++ b/core/io/resource_uid.h @@ -37,6 +37,8 @@ class FileAccess; +typedef void (*ResourceUIDScanForUIDOnStartup)(); + class ResourceUID : public Object { GDCLASS(ResourceUID, Object) public: @@ -63,6 +65,8 @@ protected: static void _bind_methods(); public: + inline static ResourceUIDScanForUIDOnStartup scan_for_uid_on_startup = nullptr; + String id_to_text(ID p_id) const; ID text_to_id(const String &p_text) const; diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 2d10e4305ea..4c5663c3b99 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -48,6 +48,9 @@ #include "scene/resources/packed_scene.h" EditorFileSystem *EditorFileSystem::singleton = nullptr; +int EditorFileSystem::nb_files_total = 0; +EditorFileSystem::ScannedDirectory *EditorFileSystem::first_scan_root_dir = nullptr; + //the name is the version, to keep compatibility with different versions of Godot #define CACHE_FILE_NAME "filesystem_cache10" @@ -237,16 +240,72 @@ EditorFileSystem::ScannedDirectory::~ScannedDirectory() { } } -void EditorFileSystem::_first_scan_filesystem() { - EditorProgress ep = EditorProgress("first_scan_filesystem", TTR("Project initialization"), 5); +void EditorFileSystem::_load_first_scan_root_dir() { Ref d = DirAccess::create(DirAccess::ACCESS_RESOURCES); first_scan_root_dir = memnew(ScannedDirectory); first_scan_root_dir->full_path = "res://"; + + nb_files_total = _scan_new_dir(first_scan_root_dir, d); +} + +void EditorFileSystem::scan_for_uid() { + // Load file structure into memory. + _load_first_scan_root_dir(); + + // Load extensions for which an .import should exists. + List extensionsl; + HashSet import_extensions; + ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl); + for (const String &E : extensionsl) { + import_extensions.insert(E); + } + + // Scan the file system to load uid. + _scan_for_uid_directory(first_scan_root_dir, import_extensions); + + // It's done, resetting the callback method to prevent a second scan. + ResourceUID::scan_for_uid_on_startup = nullptr; +} + +void EditorFileSystem::_scan_for_uid_directory(const ScannedDirectory *p_scan_dir, const HashSet &p_import_extensions) { + for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { + _scan_for_uid_directory(scan_sub_dir, p_import_extensions); + } + + for (const String &scan_file : p_scan_dir->files) { + const String ext = scan_file.get_extension().to_lower(); + + if (ext == "uid" || ext == "import") { + continue; + } + + const String path = p_scan_dir->full_path.path_join(scan_file); + ResourceUID::ID uid = ResourceUID::INVALID_ID; + if (p_import_extensions.has(ext)) { + if (FileAccess::exists(path + ".import")) { + uid = ResourceFormatImporter::get_singleton()->get_resource_uid(path); + } + } else { + uid = ResourceLoader::get_resource_uid(path); + } + + if (uid != ResourceUID::INVALID_ID) { + if (!ResourceUID::get_singleton()->has_id(uid)) { + ResourceUID::get_singleton()->add_id(uid, path); + } + } + } +} + +void EditorFileSystem::_first_scan_filesystem() { + EditorProgress ep = EditorProgress("first_scan_filesystem", TTR("Project initialization"), 5); HashSet existing_class_names; HashSet extensions; - ep.step(TTR("Scanning file structure..."), 0, true); - nb_files_total = _scan_new_dir(first_scan_root_dir, d); + if (!first_scan_root_dir) { + ep.step(TTR("Scanning file structure..."), 0, true); + _load_first_scan_root_dir(); + } // Preloading GDExtensions file extensions to prevent looping on all the resource loaders // for each files in _first_scan_process_scripts. @@ -440,6 +499,7 @@ void EditorFileSystem::_scan_filesystem() { sd = first_scan_root_dir; // Will be updated on scan. ResourceUID::get_singleton()->clear(); + ResourceUID::scan_for_uid_on_startup = nullptr; processed_files = memnew(HashSet()); } else { Ref d = DirAccess::create(DirAccess::ACCESS_RESOURCES); diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 5b78b86a3f3..4f9302e045f 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -182,7 +182,7 @@ class EditorFileSystem : public Node { static void _thread_func(void *_userdata); EditorFileSystemDirectory *new_filesystem = nullptr; - ScannedDirectory *first_scan_root_dir = nullptr; + static ScannedDirectory *first_scan_root_dir; bool filesystem_changed_queued = false; bool scanning = false; @@ -192,13 +192,17 @@ class EditorFileSystem : public Node { float scan_total; String filesystem_settings_version_for_import; bool revalidate_import_files = false; - int nb_files_total = 0; + static int nb_files_total; void _notify_filesystem_changed(); void _scan_filesystem(); void _first_scan_filesystem(); void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, List &p_gdextension_extensions, HashSet &p_existing_class_names, HashSet &p_extensions); + static void _scan_for_uid_directory(const ScannedDirectory *p_scan_dir, const HashSet &p_import_extensions); + + static void _load_first_scan_root_dir(); + HashSet late_update_files; void _save_late_updated_files(); @@ -255,7 +259,7 @@ class EditorFileSystem : public Node { HashSet valid_extensions; HashSet import_extensions; - int _scan_new_dir(ScannedDirectory *p_dir, Ref &da); + static int _scan_new_dir(ScannedDirectory *p_dir, Ref &da); void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet *p_processed_files); Thread thread_sources; @@ -412,6 +416,8 @@ public: static bool _should_skip_directory(const String &p_path); + static void scan_for_uid(); + void add_import_format_support_query(Ref p_query); void remove_import_format_support_query(Ref p_query); EditorFileSystem(); diff --git a/main/main.cpp b/main/main.cpp index c65e6ba8acb..6cd9c89fa8b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3444,6 +3444,17 @@ Error Main::setup2(bool p_show_boot_logo) { // This loads global classes, so it must happen before custom loaders and savers are registered ScriptServer::init_languages(); +#if TOOLS_ENABLED + + // Setting up the callback to execute a scan for UIDs on disk when a UID + // does not exist in the UID cache on startup. This prevents invalid UID errors + // when opening a project without a UID cache file or with an invalid cache. + if (editor) { + ResourceUID::scan_for_uid_on_startup = EditorFileSystem::scan_for_uid; + } + +#endif + theme_db->initialize_theme(); audio_server->load_default_bus_layout(); @@ -3496,9 +3507,21 @@ void Main::setup_boot_logo() { if (show_logo) { //boot logo! const bool boot_logo_image = GLOBAL_DEF_BASIC("application/boot_splash/show_image", true); - const String boot_logo_path = ResourceUID::ensure_path(GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png"), String())).strip_edges(); const bool boot_logo_scale = GLOBAL_DEF_BASIC("application/boot_splash/fullsize", true); const bool boot_logo_filter = GLOBAL_DEF_BASIC("application/boot_splash/use_filter", true); + String boot_logo_path = GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/boot_splash/image", PROPERTY_HINT_FILE, "*.png"), String()); + + // If the UID cache is missing or invalid, it could be 'normal' for the UID to not exist in memory. + // It's too soon to scan the project files since the ResourceFormatImporter is not loaded yet, + // so to prevent printing errors, we will just skip the custom boot logo this time. + if (boot_logo_path.begins_with("uid://")) { + const ResourceUID::ID logo_id = ResourceUID::get_singleton()->text_to_id(boot_logo_path); + if (ResourceUID::get_singleton()->has_id(logo_id)) { + boot_logo_path = ResourceUID::get_singleton()->get_id_path(logo_id).strip_edges(); + } else { + boot_logo_path = String(); + } + } Ref boot_logo; @@ -4215,7 +4238,7 @@ int Main::start() { #ifdef TOOLS_ENABLED if (editor) { - if (!recovery_mode && (game_path != String(GLOBAL_GET("application/run/main_scene")) || !editor_node->has_scenes_in_session())) { + if (!recovery_mode && (game_path != ResourceUID::ensure_path(String(GLOBAL_GET("application/run/main_scene"))) || !editor_node->has_scenes_in_session())) { Error serr = editor_node->load_scene(local_game_path); if (serr != OK) { ERR_PRINT("Failed to load scene");