Browse Source

Support script global resource name in EditorFileSystem

* Works for binary and text files.
* Makes EditorQuickOpen work with custom resources again.
* Information is cached and easily accessible.

Properly fixes #66179. Supersedes #66215 and supersedes #62417

**WARNING**: This required breaking backwards binary compatibility (.res and .scn files). Files saved after this PR is merged will no longer open in any earlier versions of Godot.
Juan Linietsky 2 years ago
parent
commit
dddd8d43f6

+ 92 - 1
core/io/resource_format_binary.cpp

@@ -91,7 +91,8 @@ enum {
 	// Version 2: added 64 bits support for float and int.
 	// Version 3: changed nodepath encoding.
 	// Version 4: new string ID for ext/subresources, breaks forward compat.
-	FORMAT_VERSION = 4,
+	// Version 5: Ability to store script class in the header.
+	FORMAT_VERSION = 5,
 	FORMAT_VERSION_CAN_RENAME_DEPS = 1,
 	FORMAT_VERSION_NO_NODEPATH_PROPERTY = 3,
 };
@@ -1013,6 +1014,10 @@ void ResourceLoaderBinary::open(Ref<FileAccess> p_f, bool p_no_resources, bool p
 		uid = ResourceUID::INVALID_ID;
 	}
 
+	if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS) {
+		script_class = get_unicode_string();
+	}
+
 	for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
 		f->get_32(); //skip a few reserved fields
 	}
@@ -1117,6 +1122,57 @@ String ResourceLoaderBinary::recognize(Ref<FileAccess> p_f) {
 	return get_unicode_string();
 }
 
+String ResourceLoaderBinary::recognize_script_class(Ref<FileAccess> p_f) {
+	error = OK;
+
+	f = p_f;
+	uint8_t header[4];
+	f->get_buffer(header, 4);
+	if (header[0] == 'R' && header[1] == 'S' && header[2] == 'C' && header[3] == 'C') {
+		// Compressed.
+		Ref<FileAccessCompressed> fac;
+		fac.instantiate();
+		error = fac->open_after_magic(f);
+		if (error != OK) {
+			f.unref();
+			return "";
+		}
+		f = fac;
+
+	} else if (header[0] != 'R' || header[1] != 'S' || header[2] != 'R' || header[3] != 'C') {
+		// Not normal.
+		error = ERR_FILE_UNRECOGNIZED;
+		f.unref();
+		return "";
+	}
+
+	bool big_endian = f->get_32();
+	f->get_32(); // use_real64
+
+	f->set_big_endian(big_endian != 0); //read big endian if saved as big endian
+
+	uint32_t ver_major = f->get_32();
+	f->get_32(); // ver_minor
+	uint32_t ver_fmt = f->get_32();
+
+	if (ver_fmt > FORMAT_VERSION || ver_major > VERSION_MAJOR) {
+		f.unref();
+		return "";
+	}
+
+	get_unicode_string(); // type
+
+	f->get_64(); // Metadata offset
+	uint32_t flags = f->get_32();
+	f->get_64(); // UID
+
+	if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS) {
+		return get_unicode_string();
+	} else {
+		return String();
+	}
+}
+
 Ref<Resource> ResourceFormatLoaderBinary::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
 	if (r_error) {
 		*r_error = ERR_FILE_CANT_OPEN;
@@ -1299,6 +1355,9 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
 
 	fw->store_32(flags);
 	fw->store_64(uid_data);
+	if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS) {
+		save_ustring(fw, get_ustring(f));
+	}
 
 	for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
 		fw->store_32(0); // reserved
@@ -1420,6 +1479,18 @@ String ResourceFormatLoaderBinary::get_resource_type(const String &p_path) const
 	return ClassDB::get_compatibility_remapped_class(r);
 }
 
+String ResourceFormatLoaderBinary::get_resource_script_class(const String &p_path) const {
+	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+	if (f.is_null()) {
+		return ""; //could not read
+	}
+
+	ResourceLoaderBinary loader;
+	loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
+	loader.res_path = loader.local_path;
+	return loader.recognize_script_class(f);
+}
+
 ResourceUID::ID ResourceFormatLoaderBinary::get_resource_uid(const String &p_path) const {
 	String ext = p_path.get_extension().to_lower();
 	if (!ClassDB::is_resource_extension(ext)) {
@@ -2037,15 +2108,31 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const Ref<Re
 
 	save_unicode_string(f, _resource_get_class(p_resource));
 	f->store_64(0); //offset to import metadata
+
+	String script_class;
 	{
 		uint32_t format_flags = FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS;
 #ifdef REAL_T_IS_DOUBLE
 		format_flags |= FORMAT_FLAG_REAL_T_IS_DOUBLE;
 #endif
+		if (!p_resource->is_class("PackedScene")) {
+			Ref<Script> s = p_resource->get_script();
+			if (s.is_valid()) {
+				script_class = s->get_global_name();
+				if (!script_class.is_empty()) {
+					format_flags |= ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS;
+				}
+			}
+		}
+
 		f->store_32(format_flags);
 	}
 	ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true);
 	f->store_64(uid);
+	if (!script_class.is_empty()) {
+		save_unicode_string(f, script_class);
+	}
+
 	for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
 		f->store_32(0); // reserved
 	}
@@ -2298,6 +2385,10 @@ Error ResourceFormatSaverBinaryInstance::set_uid(const String &p_path, ResourceU
 	fw->store_32(flags);
 	fw->store_64(p_uid);
 
+	if (flags & ResourceFormatSaverBinaryInstance::FORMAT_FLAG_HAS_SCRIPT_CLASS) {
+		save_ustring(fw, get_ustring(f));
+	}
+
 	//rest of file
 	uint8_t b = f->get_8();
 	while (!f->eof_reached()) {

+ 4 - 0
core/io/resource_format_binary.h

@@ -65,6 +65,7 @@ class ResourceLoaderBinary {
 
 	bool using_named_scene_ids = false;
 	bool using_uids = false;
+	String script_class;
 	bool use_sub_threads = false;
 	float *progress = nullptr;
 	Vector<ExtResource> external_resources;
@@ -100,6 +101,7 @@ public:
 	void set_remaps(const HashMap<String, String> &p_remaps) { remaps = p_remaps; }
 	void open(Ref<FileAccess> p_f, bool p_no_resources = false, bool p_keep_uuid_paths = false);
 	String recognize(Ref<FileAccess> p_f);
+	String recognize_script_class(Ref<FileAccess> p_f);
 	void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types);
 	void get_classes_used(Ref<FileAccess> p_f, HashSet<StringName> *p_classes);
 
@@ -113,6 +115,7 @@ public:
 	virtual void get_recognized_extensions(List<String> *p_extensions) const;
 	virtual bool handles_type(const String &p_type) const;
 	virtual String get_resource_type(const String &p_path) const;
+	virtual String get_resource_script_class(const String &p_path) const;
 	virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
 	virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
 	virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
@@ -165,6 +168,7 @@ public:
 		FORMAT_FLAG_NAMED_SCENE_IDS = 1,
 		FORMAT_FLAG_UIDS = 2,
 		FORMAT_FLAG_REAL_T_IS_DOUBLE = 4,
+		FORMAT_FLAG_HAS_SCRIPT_CLASS = 8,
 
 		// Amount of reserved 32-bit fields in resource header
 		RESERVED_FIELDS = 11

+ 20 - 0
core/io/resource_loader.cpp

@@ -99,6 +99,12 @@ String ResourceFormatLoader::get_resource_type(const String &p_path) const {
 	return ret;
 }
 
+String ResourceFormatLoader::get_resource_script_class(const String &p_path) const {
+	String ret;
+	GDVIRTUAL_CALL(_get_resource_script_class, p_path, ret);
+	return ret;
+}
+
 ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const {
 	int64_t uid = ResourceUID::INVALID_ID;
 	GDVIRTUAL_CALL(_get_resource_uid, p_path, uid);
@@ -184,6 +190,7 @@ void ResourceFormatLoader::_bind_methods() {
 	GDVIRTUAL_BIND(_recognize_path, "path", "type");
 	GDVIRTUAL_BIND(_handles_type, "type");
 	GDVIRTUAL_BIND(_get_resource_type, "path");
+	GDVIRTUAL_BIND(_get_resource_script_class, "path");
 	GDVIRTUAL_BIND(_get_resource_uid, "path");
 	GDVIRTUAL_BIND(_get_dependencies, "path", "add_types");
 	GDVIRTUAL_BIND(_rename_dependencies, "path", "renames");
@@ -764,6 +771,19 @@ String ResourceLoader::get_resource_type(const String &p_path) {
 	return "";
 }
 
+String ResourceLoader::get_resource_script_class(const String &p_path) {
+	String local_path = _validate_local_path(p_path);
+
+	for (int i = 0; i < loader_count; i++) {
+		String result = loader[i]->get_resource_script_class(local_path);
+		if (!result.is_empty()) {
+			return result;
+		}
+	}
+
+	return "";
+}
+
 ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
 	String local_path = _validate_local_path(p_path);
 

+ 3 - 0
core/io/resource_loader.h

@@ -54,6 +54,7 @@ protected:
 	GDVIRTUAL2RC(bool, _recognize_path, String, StringName)
 	GDVIRTUAL1RC(bool, _handles_type, StringName)
 	GDVIRTUAL1RC(String, _get_resource_type, String)
+	GDVIRTUAL1RC(String, _get_resource_script_class, String)
 	GDVIRTUAL1RC(ResourceUID::ID, _get_resource_uid, String)
 	GDVIRTUAL2RC(Vector<String>, _get_dependencies, String, bool)
 	GDVIRTUAL1RC(Vector<String>, _get_classes_used, String)
@@ -71,6 +72,7 @@ public:
 	virtual bool handles_type(const String &p_type) const;
 	virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
 	virtual String get_resource_type(const String &p_path) const;
+	virtual String get_resource_script_class(const String &p_path) const;
 	virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
 	virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
 	virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);
@@ -175,6 +177,7 @@ public:
 	static void remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader);
 	static void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
 	static String get_resource_type(const String &p_path);
+	static String get_resource_script_class(const String &p_path);
 	static ResourceUID::ID get_resource_uid(const String &p_path);
 	static void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
 	static Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);

+ 1 - 1
core/object/script_language.h

@@ -120,7 +120,7 @@ public:
 	virtual bool can_instantiate() const = 0;
 
 	virtual Ref<Script> get_base_script() const = 0; //for script inheritance
-
+	virtual StringName get_global_name() const = 0;
 	virtual bool inherits_script(const Ref<Script> &p_script) const = 0;
 
 	virtual StringName get_instance_base_type() const = 0; // this may not work in all scripts, will return empty if so

+ 1 - 0
core/object/script_language_extension.h

@@ -53,6 +53,7 @@ protected:
 public:
 	EXBIND0RC(bool, can_instantiate)
 	EXBIND0RC(Ref<Script>, get_base_script)
+	EXBIND0RC(StringName, get_global_name)
 	EXBIND1RC(bool, inherits_script, const Ref<Script> &)
 	EXBIND0RC(StringName, get_instance_base_type)
 

+ 7 - 0
doc/classes/ResourceFormatLoader.xml

@@ -38,6 +38,13 @@
 				Gets the list of extensions for files this loader is able to read.
 			</description>
 		</method>
+		<method name="_get_resource_script_class" qualifiers="virtual const">
+			<return type="String" />
+			<param index="0" name="path" type="String" />
+			<description>
+				Returns the script class name associated with the [Resource] under the given [param path]. If the resource has no script or the script isn't a named class, it should return [code]""[/code].
+			</description>
+		</method>
 		<method name="_get_resource_type" qualifiers="virtual const">
 			<return type="String" />
 			<param index="0" name="path" type="String" />

+ 24 - 2
editor/editor_file_system.cpp

@@ -47,7 +47,7 @@
 
 EditorFileSystem *EditorFileSystem::singleton = nullptr;
 //the name is the version, to keep compatibility with different versions of Godot
-#define CACHE_FILE_NAME "filesystem_cache7"
+#define CACHE_FILE_NAME "filesystem_cache8"
 
 void EditorFileSystemDirectory::sort_files() {
 	files.sort_custom<FileInfoSort>();
@@ -169,6 +169,11 @@ StringName EditorFileSystemDirectory::get_file_type(int p_idx) const {
 	return files[p_idx]->type;
 }
 
+StringName EditorFileSystemDirectory::get_file_resource_script_class(int p_idx) const {
+	ERR_FAIL_INDEX_V(p_idx, files.size(), "");
+	return files[p_idx]->resource_script_class;
+}
+
 String EditorFileSystemDirectory::get_name() {
 	return name;
 }
@@ -266,6 +271,10 @@ void EditorFileSystem::_scan_filesystem() {
 
 					FileCache fc;
 					fc.type = split[1];
+					if (fc.type.find("/") != -1) {
+						fc.type = fc.type.get_slice("/", 0);
+						fc.resource_script_class = fc.type.get_slice("/", 1);
+					}
 					fc.uid = split[2].to_int();
 					fc.modification_time = split[3].to_int();
 					fc.import_modification_time = split[4].to_int();
@@ -854,6 +863,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 
 			if (fc && fc->modification_time == mt && fc->import_modification_time == import_mt && !_test_for_reimport(path, true)) {
 				fi->type = fc->type;
+				fi->resource_script_class = fc->resource_script_class;
 				fi->uid = fc->uid;
 				fi->deps = fc->deps;
 				fi->modified_time = fc->modification_time;
@@ -875,6 +885,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 
 				if (fc->type.is_empty()) {
 					fi->type = ResourceLoader::get_resource_type(path);
+					fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
 					fi->import_group_file = ResourceLoader::get_import_group_file(path);
 					//there is also the chance that file type changed due to reimport, must probably check this somehow here (or kind of note it for next time in another file?)
 					//note: I think this should not happen any longer..
@@ -904,6 +915,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 			if (fc && fc->modification_time == mt) {
 				//not imported, so just update type if changed
 				fi->type = fc->type;
+				fi->resource_script_class = fc->resource_script_class;
 				fi->uid = fc->uid;
 				fi->modified_time = fc->modification_time;
 				fi->deps = fc->deps;
@@ -915,6 +927,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
 			} else {
 				//new or modified time
 				fi->type = ResourceLoader::get_resource_type(path);
+				fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
 				if (fi->type == "" && textfile_extensions.has(ext)) {
 					fi->type = "TextFile";
 				}
@@ -1029,6 +1042,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, const
 					fi->modified_time = FileAccess::get_modified_time(path);
 					fi->import_modified_time = 0;
 					fi->type = ResourceLoader::get_resource_type(path);
+					fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
 					if (fi->type == "" && textfile_extensions.has(ext)) {
 						fi->type = "TextFile";
 					}
@@ -1285,7 +1299,12 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir,
 		if (!p_dir->files[i]->import_group_file.is_empty()) {
 			group_file_cache.insert(p_dir->files[i]->import_group_file);
 		}
-		String s = p_dir->files[i]->file + "::" + p_dir->files[i]->type + "::" + itos(p_dir->files[i]->uid) + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid) + "::" + p_dir->files[i]->import_group_file + "::" + p_dir->files[i]->script_class_name + "<>" + p_dir->files[i]->script_class_extends + "<>" + p_dir->files[i]->script_class_icon_path;
+
+		String type = p_dir->files[i]->type;
+		if (p_dir->files[i]->resource_script_class) {
+			type += "/" + String(p_dir->files[i]->resource_script_class);
+		}
+		String s = p_dir->files[i]->file + "::" + type + "::" + itos(p_dir->files[i]->uid) + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid) + "::" + p_dir->files[i]->import_group_file + "::" + p_dir->files[i]->script_class_name + "<>" + p_dir->files[i]->script_class_extends + "<>" + p_dir->files[i]->script_class_icon_path;
 		s += "::";
 		for (int j = 0; j < p_dir->files[i]->deps.size(); j++) {
 			if (j > 0) {
@@ -1612,6 +1631,8 @@ void EditorFileSystem::update_file(const String &p_file) {
 	if (type.is_empty() && textfile_extensions.has(p_file.get_extension())) {
 		type = "TextFile";
 	}
+	String script_class = ResourceLoader::get_resource_script_class(p_file);
+
 	ResourceUID::ID uid = ResourceLoader::get_resource_uid(p_file);
 
 	if (cpos == -1) {
@@ -1645,6 +1666,7 @@ void EditorFileSystem::update_file(const String &p_file) {
 	}
 
 	fs->files[cpos]->type = type;
+	fs->files[cpos]->resource_script_class = script_class;
 	fs->files[cpos]->uid = uid;
 	fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends, &fs->files[cpos]->script_class_icon_path);
 	fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(p_file);

+ 4 - 0
editor/editor_file_system.h

@@ -54,6 +54,7 @@ class EditorFileSystemDirectory : public Object {
 	struct FileInfo {
 		String file;
 		StringName type;
+		StringName resource_script_class; // If any resource has script with a global class name, its found here.
 		ResourceUID::ID uid = ResourceUID::INVALID_ID;
 		uint64_t modified_time = 0;
 		uint64_t import_modified_time = 0;
@@ -61,6 +62,7 @@ class EditorFileSystemDirectory : public Object {
 		String import_group_file;
 		Vector<String> deps;
 		bool verified = false; //used for checking changes
+		// These are for script resources only.
 		String script_class_name;
 		String script_class_extends;
 		String script_class_icon_path;
@@ -90,6 +92,7 @@ public:
 	String get_file(int p_idx) const;
 	String get_file_path(int p_idx) const;
 	StringName get_file_type(int p_idx) const;
+	StringName get_file_resource_script_class(int p_idx) const;
 	Vector<String> get_file_deps(int p_idx) const;
 	bool get_file_import_is_valid(int p_idx) const;
 	uint64_t get_file_modified_time(int p_idx) const;
@@ -189,6 +192,7 @@ class EditorFileSystem : public Node {
 	/* Used for reading the filesystem cache file */
 	struct FileCache {
 		String type;
+		String resource_script_class;
 		ResourceUID::ID uid = ResourceUID::INVALID_ID;
 		uint64_t modification_time = 0;
 		uint64_t import_modification_time = 0;

+ 3 - 11
editor/editor_quick_open.cpp

@@ -69,17 +69,9 @@ void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
 	for (int i = 0; i < p_efsd->get_file_count(); i++) {
 		String file = p_efsd->get_file_path(i);
 		String engine_type = p_efsd->get_file_type(i);
-		// TODO: Fix lack of caching for resource's script's global class name (if applicable).
-		String script_type;
-		if (_load_resources) {
-			Ref<Resource> res = ResourceLoader::load(file);
-			if (res.is_valid()) {
-				Ref<Script> scr = res->get_script();
-				if (scr.is_valid()) {
-					script_type = scr->get_language()->get_global_class_name(file);
-				}
-			}
-		}
+
+		String script_type = p_efsd->get_file_resource_script_class(i);
+
 		String actual_type = script_type.is_empty() ? engine_type : script_type;
 		// Iterate all possible base types.
 		for (String &parent_type : base_types) {

+ 0 - 1
editor/editor_quick_open.h

@@ -43,7 +43,6 @@ class EditorQuickOpen : public ConfirmationDialog {
 	Tree *search_options = nullptr;
 	String base_type;
 	bool allow_multi_select = false;
-	bool _load_resources = false; // Prohibitively slow for now.
 
 	Vector<String> files;
 	OAHashMap<String, Ref<Texture2D>> icons;

+ 4 - 0
modules/gdscript/gdscript.cpp

@@ -248,6 +248,10 @@ Ref<Script> GDScript::get_base_script() const {
 	}
 }
 
+StringName GDScript::get_global_name() const {
+	return name;
+}
+
 StringName GDScript::get_instance_base_type() const {
 	if (native.is_valid()) {
 		return native->get_name();

+ 1 - 0
modules/gdscript/gdscript.h

@@ -228,6 +228,7 @@ public:
 	virtual bool can_instantiate() const override;
 
 	virtual Ref<Script> get_base_script() const override;
+	virtual StringName get_global_name() const override;
 
 	virtual StringName get_instance_base_type() const override; // this may not work in all scripts, will return empty if so
 	virtual ScriptInstance *instance_create(Object *p_this) override;

+ 4 - 0
modules/mono/csharp_script.cpp

@@ -2606,6 +2606,10 @@ Ref<Script> CSharpScript::get_base_script() const {
 	return base_script;
 }
 
+StringName CSharpScript::get_global_name() const {
+	return StringName();
+}
+
 void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
 #ifdef TOOLS_ENABLED
 	const CSharpScript *top = this;

+ 2 - 0
modules/mono/csharp_script.h

@@ -186,6 +186,8 @@ public:
 	bool inherits_script(const Ref<Script> &p_script) const override;
 
 	Ref<Script> get_base_script() const override;
+	StringName get_global_name() const override;
+
 	ScriptLanguage *get_language() const override;
 
 	void get_script_method_list(List<MethodInfo> *p_list) const override;

+ 77 - 2
scene/resources/resource_format_text.cpp

@@ -927,7 +927,11 @@ Error ResourceLoaderText::rename_dependencies(Ref<FileAccess> p_f, const String
 				if (is_scene) {
 					fw->store_line("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + "]\n");
 				} else {
-					fw->store_line("[gd_resource type=\"" + res_type + "\" load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + "]\n");
+					String script_res_text;
+					if (!script_class.is_empty()) {
+						script_res_text = "script_class=\"" + script_class + "\" ";
+					}
+					fw->store_line("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + "]\n");
 				}
 			}
 
@@ -1051,6 +1055,10 @@ void ResourceLoaderText::open(Ref<FileAccess> p_f, bool p_skip_first_tag) {
 			return;
 		}
 
+		if (tag.fields.has("script_class")) {
+			script_class = tag.fields["script_class"];
+		}
+
 		res_type = tag.fields["type"];
 
 	} else {
@@ -1497,6 +1505,44 @@ Error ResourceLoaderText::get_classes_used(HashSet<StringName> *r_classes) {
 	return OK;
 }
 
+String ResourceLoaderText::recognize_script_class(Ref<FileAccess> p_f) {
+	error = OK;
+
+	lines = 1;
+	f = p_f;
+
+	stream.f = f;
+
+	ignore_resource_parsing = true;
+
+	VariantParser::Tag tag;
+	Error err = VariantParser::parse_tag(&stream, lines, error_text, tag);
+
+	if (err) {
+		_printerr();
+		return "";
+	}
+
+	if (tag.fields.has("format")) {
+		int fmt = tag.fields["format"];
+		if (fmt > FORMAT_VERSION) {
+			error_text = "Saved with newer format version";
+			_printerr();
+			return "";
+		}
+	}
+
+	if (tag.name != "gd_resource") {
+		return "";
+	}
+
+	if (tag.fields.has("script_class")) {
+		return tag.fields["script_class"];
+	}
+
+	return "";
+}
+
 String ResourceLoaderText::recognize(Ref<FileAccess> p_f) {
 	error = OK;
 
@@ -1666,6 +1712,25 @@ String ResourceFormatLoaderText::get_resource_type(const String &p_path) const {
 	return ClassDB::get_compatibility_remapped_class(r);
 }
 
+String ResourceFormatLoaderText::get_resource_script_class(const String &p_path) const {
+	String ext = p_path.get_extension().to_lower();
+	if (ext != "tres") {
+		return String();
+	}
+
+	// ...for anything else must test...
+
+	Ref<FileAccess> f = FileAccess::open(p_path, FileAccess::READ);
+	if (f.is_null()) {
+		return ""; //could not read
+	}
+
+	ResourceLoaderText loader;
+	loader.local_path = ProjectSettings::get_singleton()->localize_path(p_path);
+	loader.res_path = loader.local_path;
+	return loader.recognize_script_class(f);
+}
+
 ResourceUID::ID ResourceFormatLoaderText::get_resource_uid(const String &p_path) const {
 	String ext = p_path.get_extension().to_lower();
 
@@ -1909,7 +1974,12 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso
 		String title = packed_scene.is_valid() ? "[gd_scene " : "[gd_resource ";
 		if (packed_scene.is_null()) {
 			title += "type=\"" + _resource_get_class(p_resource) + "\" ";
+			Ref<Script> script = p_resource->get_script();
+			if (script.is_valid() && script->get_global_name()) {
+				title += "script_class=\"" + String(script->get_global_name()) + "\" ";
+			}
 		}
+
 		int load_steps = saved_resources.size() + external_resources.size();
 
 		if (load_steps > 1) {
@@ -2248,7 +2318,12 @@ Error ResourceLoaderText::set_uid(Ref<FileAccess> p_f, ResourceUID::ID p_uid) {
 	if (is_scene) {
 		fw->store_string("[gd_scene load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
 	} else {
-		fw->store_string("[gd_resource type=\"" + res_type + "\" load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
+		String script_res_text;
+		if (!script_class.is_empty()) {
+			script_res_text = "script_class=\"" + script_class + "\" ";
+		}
+
+		fw->store_string("[gd_resource type=\"" + res_type + "\" " + script_res_text + "load_steps=" + itos(resources_total) + " format=" + itos(FORMAT_VERSION) + " uid=\"" + ResourceUID::get_singleton()->id_to_text(p_uid) + "\"]");
 	}
 
 	uint8_t c = f->get_8();

+ 3 - 0
scene/resources/resource_format_text.h

@@ -64,6 +64,7 @@ class ResourceLoaderText {
 	int resources_total = 0;
 	int resource_current = 0;
 	String resource_type;
+	String script_class;
 
 	VariantParser::Tag next_tag;
 
@@ -125,6 +126,7 @@ public:
 
 	void open(Ref<FileAccess> p_f, bool p_skip_first_tag = false);
 	String recognize(Ref<FileAccess> p_f);
+	String recognize_script_class(Ref<FileAccess> p_f);
 	ResourceUID::ID get_uid(Ref<FileAccess> p_f);
 	void get_dependencies(Ref<FileAccess> p_f, List<String> *p_dependencies, bool p_add_types);
 	Error rename_dependencies(Ref<FileAccess> p_f, const String &p_path, const HashMap<String, String> &p_map);
@@ -144,6 +146,7 @@ public:
 	virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes);
 
 	virtual String get_resource_type(const String &p_path) const;
+	virtual String get_resource_script_class(const String &p_path) const;
 	virtual ResourceUID::ID get_resource_uid(const String &p_path) const;
 	virtual void get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types = false);
 	virtual Error rename_dependencies(const String &p_path, const HashMap<String, String> &p_map);