Просмотр исходного кода

Global class names (and GDScript support for it)

Juan Linietsky 7 лет назад
Родитель
Сommit
a3f1ee5c57

+ 5 - 1
core/project_settings.cpp

@@ -515,7 +515,11 @@ Error ProjectSettings::_load_settings_text(const String p_path) {
 				}
 			} else {
 				// config_version is checked and dropped
-				set(section + "/" + assign, value);
+				if (section == String()) {
+					set(assign, value);
+				} else {
+					set(section + "/" + assign, value);
+				}
 			}
 		} else if (next_tag.name != String()) {
 			section = next_tag.name;

+ 77 - 0
core/script_language.cpp

@@ -29,6 +29,7 @@
 /*************************************************************************/
 
 #include "script_language.h"
+#include "project_settings.h"
 
 ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
 int ScriptServer::_language_count = 0;
@@ -103,6 +104,20 @@ void ScriptServer::unregister_language(ScriptLanguage *p_language) {
 
 void ScriptServer::init_languages() {
 
+	{ //load global classes
+		global_classes_clear();
+		if (ProjectSettings::get_singleton()->has_setting("_global_script_classes")) {
+			Array script_classes = ProjectSettings::get_singleton()->get("_global_script_classes");
+
+			for (int i = 0; i < script_classes.size(); i++) {
+				Dictionary c = script_classes[i];
+				if (!c.has("class") || !c.has("language") || !c.has("path") || !c.has("base"))
+					continue;
+				add_global_class(c["class"], c["base"], c["language"], c["path"]);
+			}
+		}
+	}
+
 	for (int i = 0; i < _language_count; i++) {
 		_languages[i]->init();
 	}
@@ -113,6 +128,7 @@ void ScriptServer::finish_languages() {
 	for (int i = 0; i < _language_count; i++) {
 		_languages[i]->finish();
 	}
+	global_classes_clear();
 }
 
 void ScriptServer::set_reload_scripts_on_save(bool p_enable) {
@@ -139,6 +155,67 @@ void ScriptServer::thread_exit() {
 	}
 }
 
+HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
+
+void ScriptServer::global_classes_clear() {
+	global_classes.clear();
+}
+
+void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) {
+	GlobalScriptClass g;
+	g.language = p_language;
+	g.path = p_path;
+	g.base = p_base;
+	global_classes[p_class] = g;
+}
+void ScriptServer::remove_global_class(const StringName &p_class) {
+	global_classes.erase(p_class);
+}
+bool ScriptServer::is_global_class(const StringName &p_class) {
+	return global_classes.has(p_class);
+}
+StringName ScriptServer::get_global_class_language(const StringName &p_class) {
+	ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
+	return global_classes[p_class].language;
+}
+String ScriptServer::get_global_class_path(const String &p_class) {
+	ERR_FAIL_COND_V(!global_classes.has(p_class), String());
+	return global_classes[p_class].path;
+}
+
+StringName ScriptServer::get_global_class_base(const String &p_class) {
+	ERR_FAIL_COND_V(!global_classes.has(p_class), String());
+	return global_classes[p_class].base;
+}
+void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) {
+	const StringName *K = NULL;
+	List<StringName> classes;
+	while ((K = global_classes.next(K))) {
+		classes.push_back(*K);
+	}
+	classes.sort_custom<StringName::AlphCompare>();
+	for (List<StringName>::Element *E = classes.front(); E; E = E->next()) {
+		r_global_classes->push_back(E->get());
+	}
+}
+void ScriptServer::save_global_classes() {
+	List<StringName> gc;
+	get_global_class_list(&gc);
+	Array gcarr;
+	for (List<StringName>::Element *E = gc.front(); E; E = E->next()) {
+		Dictionary d;
+		d["class"] = E->get();
+		d["language"] = global_classes[E->get()].language;
+		d["path"] = global_classes[E->get()].path;
+		d["base"] = global_classes[E->get()].base;
+		gcarr.push_back(d);
+	}
+
+	ProjectSettings::get_singleton()->set("_global_script_classes", gcarr);
+	ProjectSettings::get_singleton()->save();
+}
+
+////////////////////
 void ScriptInstance::get_property_state(List<Pair<StringName, Variant> > &state) {
 
 	List<PropertyInfo> pinfo;

+ 22 - 1
core/script_language.h

@@ -54,6 +54,14 @@ class ScriptServer {
 	static bool scripting_enabled;
 	static bool reload_scripts_on_save;
 
+	struct GlobalScriptClass {
+		StringName language;
+		String path;
+		String base;
+	};
+
+	static HashMap<StringName, GlobalScriptClass> global_classes;
+
 public:
 	static ScriptEditRequestFunction edit_request_func;
 
@@ -70,6 +78,16 @@ public:
 	static void thread_enter();
 	static void thread_exit();
 
+	static void global_classes_clear();
+	static void add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path);
+	static void remove_global_class(const StringName &p_class);
+	static bool is_global_class(const StringName &p_class);
+	static StringName get_global_class_language(const StringName &p_class);
+	static String get_global_class_path(const String &p_class);
+	static StringName get_global_class_base(const String &p_class);
+	static void get_global_class_list(List<StringName> *r_global_classes);
+	static void save_global_classes();
+
 	static void init_languages();
 	static void finish_languages();
 };
@@ -285,7 +303,10 @@ public:
 
 	virtual void frame();
 
-	virtual ~ScriptLanguage(){};
+	virtual bool handles_global_class_type(const String &p_type) const { return false; }
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL) const { return String(); }
+
+	virtual ~ScriptLanguage() {}
 };
 
 extern uint8_t script_encryption_key[32];

+ 49 - 0
editor/create_dialog.cpp

@@ -243,6 +243,18 @@ void CreateDialog::_update_search() {
 	_parse_fs(EditorFileSystem::get_singleton()->get_filesystem());
 */
 
+	List<StringName> global_classes;
+	ScriptServer::get_global_class_list(&global_classes);
+
+	Map<String, List<String> > global_class_map;
+	for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) {
+		String base = ScriptServer::get_global_class_base(E->get());
+		if (!global_class_map.has(base)) {
+			global_class_map[base] = List<String>();
+		}
+		global_class_map[base].push_back(E->get());
+	}
+
 	HashMap<String, TreeItem *> types;
 
 	TreeItem *root = search_options->create_item();
@@ -293,6 +305,32 @@ void CreateDialog::_update_search() {
 				add_type(I->get(), types, root, &to_select);
 		}
 
+		if (global_class_map.has(type) && ClassDB::is_parent_class(type, base_type)) {
+			for (List<String>::Element *J = global_class_map[type].front(); J; J = J->next()) {
+				bool show = search_box->get_text().is_subsequence_ofi(J->get());
+
+				if (!show)
+					continue;
+
+				if (!types.has(type))
+					add_type(type, types, root, &to_select);
+
+				TreeItem *ti;
+				if (types.has(type))
+					ti = types[type];
+				else
+					ti = search_options->get_root();
+
+				TreeItem *item = search_options->create_item(ti);
+				item->set_metadata(0, J->get());
+				item->set_text(0, J->get() + " (" + ScriptServer::get_global_class_path(J->get()).get_file() + ")");
+				item->set_icon(0, _get_editor_icon(type));
+				if (!to_select || J->get() == search_box->get_text()) {
+					to_select = item;
+				}
+			}
+		}
+
 		if (EditorNode::get_editor_data().get_custom_types().has(type) && ClassDB::is_parent_class(type, base_type)) {
 			//there are custom types based on this... cool.
 
@@ -444,6 +482,17 @@ Object *CreateDialog::instance_selected() {
 			custom = md;
 
 		if (custom != String()) {
+
+			if (ScriptServer::is_global_class(custom)) {
+				RES script = ResourceLoader::load(ScriptServer::get_global_class_path(custom));
+				ERR_FAIL_COND_V(!script.is_valid(), NULL);
+
+				Object *obj = ClassDB::instance(ScriptServer::get_global_class_base(custom));
+				ERR_FAIL_COND_V(!obj, NULL);
+
+				obj->set_script(script.get_ref_ptr());
+				return obj;
+			}
 			return EditorNode::get_editor_data().instance_custom_type(selected->get_text(0), custom);
 		} else {
 			return ClassDB::instance(selected->get_text(0));

+ 97 - 7
editor/editor_file_system.cpp

@@ -125,6 +125,14 @@ bool EditorFileSystemDirectory::get_file_import_is_valid(int p_idx) const {
 	return files[p_idx]->import_valid;
 }
 
+String EditorFileSystemDirectory::get_file_script_class_name(int p_idx) const {
+	return files[p_idx]->script_class_name;
+}
+
+String EditorFileSystemDirectory::get_file_script_class_extends(int p_idx) const {
+	return files[p_idx]->script_class_extends;
+}
+
 StringName EditorFileSystemDirectory::get_file_type(int p_idx) const {
 
 	ERR_FAIL_INDEX_V(p_idx, files.size(), "");
@@ -149,6 +157,8 @@ void EditorFileSystemDirectory::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_file", "idx"), &EditorFileSystemDirectory::get_file);
 	ClassDB::bind_method(D_METHOD("get_file_path", "idx"), &EditorFileSystemDirectory::get_file_path);
 	ClassDB::bind_method(D_METHOD("get_file_type", "idx"), &EditorFileSystemDirectory::get_file_type);
+	ClassDB::bind_method(D_METHOD("get_file_script_class_name", "idx"), &EditorFileSystemDirectory::get_file_script_class_name);
+	ClassDB::bind_method(D_METHOD("get_file_script_class_extends", "idx"), &EditorFileSystemDirectory::get_file_script_class_extends);
 	ClassDB::bind_method(D_METHOD("get_file_import_is_valid", "idx"), &EditorFileSystemDirectory::get_file_import_is_valid);
 	ClassDB::bind_method(D_METHOD("get_name"), &EditorFileSystemDirectory::get_name);
 	ClassDB::bind_method(D_METHOD("get_path"), &EditorFileSystemDirectory::get_path);
@@ -189,7 +199,7 @@ void EditorFileSystem::_scan_filesystem() {
 
 	String project = ProjectSettings::get_singleton()->get_resource_path();
 
-	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache3");
+	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache4");
 	FileAccess *f = FileAccess::open(fscache, FileAccess::READ);
 
 	if (f) {
@@ -209,7 +219,7 @@ void EditorFileSystem::_scan_filesystem() {
 
 			} else {
 				Vector<String> split = l.split("::");
-				ERR_CONTINUE(split.size() != 6);
+				ERR_CONTINUE(split.size() != 7);
 				String name = split[0];
 				String file;
 
@@ -221,8 +231,10 @@ void EditorFileSystem::_scan_filesystem() {
 				fc.modification_time = split[2].to_int64();
 				fc.import_modification_time = split[3].to_int64();
 				fc.import_valid = split[4].to_int64() != 0;
+				fc.script_class_name = split[5].get_slice("<>", 0);
+				fc.script_class_extends = split[5].get_slice("<>", 1);
 
-				String deps = split[5].strip_edges();
+				String deps = split[6].strip_edges();
 				if (deps.length()) {
 					Vector<String> dp = deps.split("<>");
 					for (int i = 0; i < dp.size(); i++) {
@@ -239,7 +251,7 @@ void EditorFileSystem::_scan_filesystem() {
 		memdelete(f);
 	}
 
-	String update_cache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update3");
+	String update_cache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update4");
 
 	if (FileAccess::exists(update_cache)) {
 		{
@@ -287,7 +299,7 @@ void EditorFileSystem::_scan_filesystem() {
 }
 
 void EditorFileSystem::_save_filesystem_cache() {
-	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache3");
+	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_cache4");
 
 	FileAccess *f = FileAccess::open(fscache, FileAccess::WRITE);
 	if (f == NULL) {
@@ -563,6 +575,7 @@ void EditorFileSystem::scan() {
 		scanning = false;
 		emit_signal("filesystem_changed");
 		emit_signal("sources_changed", sources_changed.size() > 0);
+		_queue_update_script_classes();
 
 	} else {
 
@@ -706,6 +719,9 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess
 				fi->modified_time = fc->modification_time;
 				fi->import_modified_time = fc->import_modification_time;
 				fi->import_valid = fc->import_valid;
+				fi->script_class_name = fc->script_class_name;
+				fi->script_class_extends = fc->script_class_extends;
+
 				if (fc->type == String()) {
 					fi->type = ResourceLoader::get_resource_type(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?)
@@ -715,6 +731,7 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess
 			} else {
 
 				fi->type = ResourceFormatImporter::get_singleton()->get_resource_type(path);
+				fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends);
 				fi->modified_time = 0;
 				fi->import_modified_time = 0;
 				fi->import_valid = ResourceLoader::is_import_valid(path);
@@ -734,9 +751,12 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, DirAccess
 				fi->deps = fc->deps;
 				fi->import_modified_time = 0;
 				fi->import_valid = true;
+				fi->script_class_name = fc->script_class_name;
+				fi->script_class_extends = fc->script_class_extends;
 			} else {
 				//new or modified time
 				fi->type = ResourceLoader::get_resource_type(path);
+				fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends);
 				fi->deps = _get_dependencies(path);
 				fi->modified_time = mt;
 				fi->import_modified_time = 0;
@@ -835,6 +855,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->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends);
 					fi->import_valid = ResourceLoader::is_import_valid(path);
 
 					{
@@ -1044,6 +1065,7 @@ void EditorFileSystem::_notification(int p_what) {
 						if (_update_scan_actions())
 							emit_signal("filesystem_changed");
 						emit_signal("sources_changed", sources_changed.size() > 0);
+						_queue_update_script_classes();
 					}
 				} else if (!scanning) {
 
@@ -1059,6 +1081,7 @@ void EditorFileSystem::_notification(int p_what) {
 					_update_scan_actions();
 					emit_signal("filesystem_changed");
 					emit_signal("sources_changed", sources_changed.size() > 0);
+					_queue_update_script_classes();
 				}
 			}
 		} break;
@@ -1087,7 +1110,7 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir,
 
 	for (int i = 0; i < p_dir->files.size(); i++) {
 
-		String s = p_dir->files[i]->file + "::" + p_dir->files[i]->type + "::" + itos(p_dir->files[i]->modified_time) + "::" + itos(p_dir->files[i]->import_modified_time) + "::" + itos(p_dir->files[i]->import_valid);
+		String s = p_dir->files[i]->file + "::" + p_dir->files[i]->type + "::" + 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]->script_class_name + "<>" + p_dir->files[i]->script_class_extends;
 		s += "::";
 		for (int j = 0; j < p_dir->files[i]->deps.size(); j++) {
 
@@ -1268,7 +1291,7 @@ EditorFileSystemDirectory *EditorFileSystem::get_filesystem_path(const String &p
 
 void EditorFileSystem::_save_late_updated_files() {
 	//files that already existed, and were modified, need re-scanning for dependencies upon project restart. This is done via saving this special file
-	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update3");
+	String fscache = EditorSettings::get_singleton()->get_project_settings_dir().plus_file("filesystem_update4");
 	FileAccessRef f = FileAccess::open(fscache, FileAccess::WRITE);
 	for (Set<String>::Element *E = late_update_files.front(); E; E = E->next()) {
 		f->store_line(E->get());
@@ -1293,6 +1316,67 @@ Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
 	return ret;
 }
 
+String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends) const {
+
+	for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+		if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
+			String global_name;
+			String extends;
+
+			global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends);
+			*r_extends = extends;
+			return global_name;
+		}
+	}
+	*r_extends = String();
+	return String();
+}
+
+void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) {
+	int filecount = p_dir->files.size();
+	const EditorFileSystemDirectory::FileInfo *const *files = p_dir->files.ptr();
+	for (int i = 0; i < filecount; i++) {
+		if (files[i]->script_class_name == String()) {
+			continue;
+		}
+
+		String lang;
+		for (int j = 0; j < ScriptServer::get_language_count(); j++) {
+			if (ScriptServer::get_language(j)->handles_global_class_type(files[i]->type)) {
+				lang = ScriptServer::get_language(j)->get_name();
+			}
+		}
+
+		ScriptServer::add_global_class(files[i]->script_class_name, files[i]->script_class_extends, lang, p_dir->get_file_path(i));
+	}
+	for (int i = 0; i < p_dir->get_subdir_count(); i++) {
+		_scan_script_classes(p_dir->get_subdir(i));
+	}
+}
+
+void EditorFileSystem::update_script_classes() {
+
+	if (!update_script_classes_queued)
+		return;
+
+	update_script_classes_queued = false;
+	ScriptServer::global_classes_clear();
+	if (get_filesystem()) {
+		_scan_script_classes(get_filesystem());
+	}
+
+	ScriptServer::save_global_classes();
+}
+
+void EditorFileSystem::_queue_update_script_classes() {
+	if (update_script_classes_queued) {
+		return;
+	}
+
+	update_script_classes_queued = true;
+	call_deferred("update_script_classes");
+}
+
 void EditorFileSystem::update_file(const String &p_file) {
 
 	EditorFileSystemDirectory *fs = NULL;
@@ -1311,7 +1395,9 @@ void EditorFileSystem::update_file(const String &p_file) {
 			memdelete(fs->files[cpos]);
 			fs->files.remove(cpos);
 		}
+
 		call_deferred("emit_signal", "filesystem_changed"); //update later
+		_queue_update_script_classes();
 		return;
 	}
 
@@ -1351,6 +1437,7 @@ void EditorFileSystem::update_file(const String &p_file) {
 	}
 
 	fs->files[cpos]->type = type;
+	fs->files[cpos]->script_class_name = _get_global_script_class(type, p_file, &fs->files[cpos]->script_class_extends);
 	fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
 	fs->files[cpos]->deps = _get_dependencies(p_file);
 	fs->files[cpos]->import_valid = ResourceLoader::is_import_valid(p_file);
@@ -1359,6 +1446,7 @@ void EditorFileSystem::update_file(const String &p_file) {
 	EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
 
 	call_deferred("emit_signal", "filesystem_changed"); //update later
+	_queue_update_script_classes();
 }
 
 void EditorFileSystem::_reimport_file(const String &p_file) {
@@ -1611,6 +1699,7 @@ void EditorFileSystem::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("update_file", "path"), &EditorFileSystem::update_file);
 	ClassDB::bind_method(D_METHOD("get_filesystem_path", "path"), &EditorFileSystem::get_filesystem_path);
 	ClassDB::bind_method(D_METHOD("get_file_type", "path"), &EditorFileSystem::get_file_type);
+	ClassDB::bind_method(D_METHOD("update_script_classes"), &EditorFileSystem::update_script_classes);
 
 	ADD_SIGNAL(MethodInfo("filesystem_changed"));
 	ADD_SIGNAL(MethodInfo("sources_changed", PropertyInfo(Variant::BOOL, "exist")));
@@ -1664,6 +1753,7 @@ EditorFileSystem::EditorFileSystem() {
 	memdelete(da);
 
 	scan_total = 0;
+	update_script_classes_queued = false;
 }
 
 EditorFileSystem::~EditorFileSystem() {

+ 14 - 0
editor/editor_file_system.h

@@ -58,6 +58,8 @@ class EditorFileSystemDirectory : public Object {
 		bool import_valid;
 		Vector<String> deps;
 		bool verified; //used for checking changes
+		String script_class_name;
+		String script_class_extends;
 	};
 
 	struct FileInfoSort {
@@ -86,6 +88,8 @@ public:
 	StringName get_file_type(int p_idx) const;
 	Vector<String> get_file_deps(int p_idx) const;
 	bool get_file_import_is_valid(int p_idx) const;
+	String get_file_script_class_name(int p_idx) const; //used for scripts
+	String get_file_script_class_extends(int p_idx) const; //used for scripts
 
 	EditorFileSystemDirectory *get_parent();
 
@@ -157,6 +161,8 @@ class EditorFileSystem : public Node {
 		uint64_t import_modification_time;
 		Vector<String> deps;
 		bool import_valid;
+		String script_class_name;
+		String script_class_extends;
 	};
 
 	HashMap<String, FileCache> file_cache;
@@ -215,6 +221,12 @@ class EditorFileSystem : public Node {
 		}
 	};
 
+	void _scan_script_classes(EditorFileSystemDirectory *p_dir);
+	volatile bool update_script_classes_queued;
+	void _queue_update_script_classes();
+
+	String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends) const;
+
 protected:
 	void _notification(int p_what);
 	static void _bind_methods();
@@ -237,6 +249,8 @@ public:
 
 	void reimport_files(const Vector<String> &p_files);
 
+	void update_script_classes();
+
 	EditorFileSystem();
 	~EditorFileSystem();
 };

+ 1 - 0
editor/plugins/script_editor_plugin.cpp

@@ -1872,6 +1872,7 @@ void ScriptEditor::save_all_scripts() {
 	}
 
 	_update_script_names();
+	EditorFileSystem::get_singleton()->update_script_classes();
 }
 
 void ScriptEditor::apply_scripts() const {

+ 45 - 0
modules/gdscript/gdscript.cpp

@@ -1739,6 +1739,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 		"assert",
 		"breakpoint",
 		"class",
+		"class_name",
 		"extends",
 		"is",
 		"func",
@@ -1788,6 +1789,50 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
 	}
 }
 
+bool GDScriptLanguage::handles_global_class_type(const String &p_type) const {
+
+	return p_type == "GDScript";
+}
+
+String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_base_type) const {
+
+	PoolVector<uint8_t> sourcef;
+	Error err;
+	FileAccess *f = FileAccess::open(p_path, FileAccess::READ, &err);
+	if (err) {
+		return String();
+	}
+
+	int len = f->get_len();
+	sourcef.resize(len + 1);
+	PoolVector<uint8_t>::Write w = sourcef.write();
+	int r = f->get_buffer(w.ptr(), len);
+	f->close();
+	memdelete(f);
+	ERR_FAIL_COND_V(r != len, String());
+	w[len] = 0;
+
+	String s;
+	if (s.parse_utf8((const char *)w.ptr())) {
+		return String();
+	}
+
+	GDScriptParser parser;
+
+	parser.parse(s, p_path.get_base_dir(), true, p_path);
+
+	if (parser.get_parse_tree() && parser.get_parse_tree()->type == GDScriptParser::Node::TYPE_CLASS) {
+
+		const GDScriptParser::ClassNode *c = static_cast<const GDScriptParser::ClassNode *>(parser.get_parse_tree());
+		if (r_base_type && c->extends_used && c->extends_class.size() == 1) {
+			*r_base_type = c->extends_class[0]; //todo, should work much better
+		}
+		return c->name;
+	}
+
+	return String();
+}
+
 GDScriptLanguage::GDScriptLanguage() {
 
 	calls = 0;

+ 5 - 0
modules/gdscript/gdscript.h

@@ -439,6 +439,11 @@ public:
 
 	virtual void get_recognized_extensions(List<String> *p_extensions) const;
 
+	/* GLOBAL CLASSES */
+
+	virtual bool handles_global_class_type(const String &p_type) const;
+	virtual String get_global_class_name(const String &p_path, String *r_base_type = NULL) const;
+
 	GDScriptLanguage();
 	~GDScriptLanguage();
 };

+ 35 - 0
modules/gdscript/gdscript_compiler.cpp

@@ -278,6 +278,41 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 				return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
 			}
 
+			/* TRY GLOBAL CLASSES */
+
+			if (ScriptServer::is_global_class(identifier)) {
+
+				const GDScriptParser::ClassNode *class_node = codegen.class_node;
+				while (class_node->owner) {
+					class_node = class_node->owner;
+				}
+
+				if (class_node->name == identifier) {
+					_set_error("Using own name in class file is not allowed (creates a cyclic reference)", p_expression);
+					return -1;
+				}
+
+				RES res = ResourceLoader::load(ScriptServer::get_global_class_path(identifier));
+				if (res.is_null()) {
+					_set_error("Can't load global class " + String(identifier) + ", cyclic reference?", p_expression);
+					return -1;
+				}
+
+				Variant key = res;
+				int idx;
+
+				if (!codegen.constant_map.has(key)) {
+
+					idx = codegen.constant_map.size();
+					codegen.constant_map[key] = idx;
+
+				} else {
+					idx = codegen.constant_map[key];
+				}
+
+				return idx | (GDScriptFunction::ADDR_TYPE_LOCAL_CONSTANT << GDScriptFunction::ADDR_BITS); //make it a local constant (faster access)
+			}
+
 #ifdef TOOLS_ENABLED
 			if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
 

+ 27 - 0
modules/gdscript/gdscript_parser.cpp

@@ -3111,6 +3111,28 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 					return;
 				}
 
+			} break;
+			case GDScriptTokenizer::TK_PR_CLASS_NAME: {
+
+				if (p_class->owner) {
+					_set_error("'class_name' is only valid for the main class namespace.");
+					return;
+				}
+				if (tokenizer->get_token(1) != GDScriptTokenizer::TK_IDENTIFIER) {
+
+					_set_error("'class_name' syntax: 'class_name <UniqueName>'");
+					return;
+				}
+
+				p_class->name = tokenizer->get_token_identifier(1);
+
+				if (self_path != String() && ScriptServer::is_global_class(p_class->name) && ScriptServer::get_global_class_path(p_class->name) != self_path) {
+					_set_error("Unique global class '" + p_class->name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+					return;
+				}
+
+				tokenizer->advance(2);
+
 			} break;
 			case GDScriptTokenizer::TK_PR_TOOL: {
 
@@ -3138,6 +3160,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
 				name = tokenizer->get_token_identifier(1);
 				tokenizer->advance(2);
 
+				if (ScriptServer::is_global_class(name)) {
+					_set_error("Can't override name of unique global class '" + name + "' already exists at path: " + ScriptServer::get_global_class_path(p_class->name));
+					return;
+				}
+
 				ClassNode *newclass = alloc_node<ClassNode>();
 				newclass->initializer = alloc_node<BlockNode>();
 				newclass->initializer->parent_class = newclass;

+ 3 - 1
modules/gdscript/gdscript_tokenizer.cpp

@@ -91,6 +91,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = {
 	"match",
 	"func",
 	"class",
+	"class_name",
 	"extends",
 	"is",
 	"onready",
@@ -187,6 +188,7 @@ static const _kws _keyword_list[] = {
 	//func
 	{ GDScriptTokenizer::TK_PR_FUNCTION, "func" },
 	{ GDScriptTokenizer::TK_PR_CLASS, "class" },
+	{ GDScriptTokenizer::TK_PR_CLASS_NAME, "class_name" },
 	{ GDScriptTokenizer::TK_PR_EXTENDS, "extends" },
 	{ GDScriptTokenizer::TK_PR_IS, "is" },
 	{ GDScriptTokenizer::TK_PR_ONREADY, "onready" },
@@ -1137,7 +1139,7 @@ void GDScriptTokenizerText::advance(int p_amount) {
 
 	//////////////////////////////////////////////////////////////////////////////////////////////////////
 
-#define BYTECODE_VERSION 12
+#define BYTECODE_VERSION 13
 
 Error GDScriptTokenizerBuffer::set_code_buffer(const Vector<uint8_t> &p_buffer) {
 

+ 1 - 0
modules/gdscript/gdscript_tokenizer.h

@@ -96,6 +96,7 @@ public:
 		TK_CF_MATCH,
 		TK_PR_FUNCTION,
 		TK_PR_CLASS,
+		TK_PR_CLASS_NAME,
 		TK_PR_EXTENDS,
 		TK_PR_IS,
 		TK_PR_ONREADY,