Browse Source

Enable autoload in editor

- Tool scripts will be executed and can be accessed by plugins.
- Other script languages can implement add/remove_named_global_constant
to make use of this functionality.
George Marques 7 years ago
parent
commit
decf178033

+ 2 - 0
core/script_language.h

@@ -236,6 +236,8 @@ public:
 
 	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const = 0;
 	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value) = 0;
+	virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) {}
+	virtual void remove_named_global_constant(const StringName &p_name) {}
 
 	/* MULTITHREAD FUNCTIONS */
 

+ 152 - 12
editor/editor_autoload_settings.cpp

@@ -33,6 +33,8 @@
 #include "editor_node.h"
 #include "global_constants.h"
 #include "project_settings.h"
+#include "scene/main/viewport.h"
+#include "scene/resources/packed_scene.h"
 
 #define PREVIEW_LIST_MAX_SIZE 10
 
@@ -155,8 +157,8 @@ void EditorAutoloadSettings::_autoload_edited() {
 		undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", selected_autoload, order);
 		undo_redo->add_undo_method(ProjectSettings::get_singleton(), "clear", name);
 
-		undo_redo->add_do_method(this, "update_autoload");
-		undo_redo->add_undo_method(this, "update_autoload");
+		undo_redo->add_do_method(this, "call_deferred", "update_autoload");
+		undo_redo->add_undo_method(this, "call_deferred", "update_autoload");
 
 		undo_redo->add_do_method(this, "emit_signal", autoload_changed);
 		undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
@@ -187,8 +189,8 @@ void EditorAutoloadSettings::_autoload_edited() {
 		undo_redo->add_do_method(ProjectSettings::get_singleton(), "set_order", base, order);
 		undo_redo->add_undo_method(ProjectSettings::get_singleton(), "set_order", base, order);
 
-		undo_redo->add_do_method(this, "update_autoload");
-		undo_redo->add_undo_method(this, "update_autoload");
+		undo_redo->add_do_method(this, "call_deferred", "update_autoload");
+		undo_redo->add_undo_method(this, "call_deferred", "update_autoload");
 
 		undo_redo->add_do_method(this, "emit_signal", autoload_changed);
 		undo_redo->add_undo_method(this, "emit_signal", autoload_changed);
@@ -296,6 +298,18 @@ void EditorAutoloadSettings::update_autoload() {
 
 	updating_autoload = true;
 
+	Map<String, AutoLoadInfo> to_remove;
+	Map<String, AutoLoadInfo> to_remove_singleton;
+	List<AutoLoadInfo> to_add;
+	List<String> to_add_singleton; // Only for when the node is still the same
+
+	for (List<AutoLoadInfo>::Element *E = autoload_cache.front(); E; E = E->next()) {
+		to_remove.insert(E->get().name, E->get());
+		if (E->get().is_singleton) {
+			to_remove_singleton.insert(E->get().name, E->get());
+		}
+	}
+
 	autoload_cache.clear();
 
 	tree->clear();
@@ -317,19 +331,44 @@ void EditorAutoloadSettings::update_autoload() {
 		if (name.empty())
 			continue;
 
+		AutoLoadInfo old_info;
+		if (to_remove.has(name)) {
+			old_info = to_remove[name];
+		}
+
 		AutoLoadInfo info;
-		info.name = pi.name;
-		info.order = ProjectSettings::get_singleton()->get_order(pi.name);
+		info.is_singleton = path.begins_with("*");
 
-		autoload_cache.push_back(info);
+		if (info.is_singleton) {
+			path = path.substr(1, path.length());
+		}
 
-		bool global = false;
+		info.name = name;
+		info.path = path;
+		info.order = ProjectSettings::get_singleton()->get_order(pi.name);
 
-		if (path.begins_with("*")) {
-			global = true;
-			path = path.substr(1, path.length());
+		if (old_info.name == info.name) {
+			if (old_info.path == info.path) {
+				// Still the same resource, check singleton status
+				to_remove.erase(name);
+				if (info.is_singleton) {
+					if (old_info.is_singleton) {
+						to_remove_singleton.erase(name);
+					} else {
+						to_add_singleton.push_back(name);
+					}
+				}
+			} else {
+				// Resource changed
+				to_add.push_back(info);
+			}
+		} else {
+			// New autoload
+			to_add.push_back(info);
 		}
 
+		autoload_cache.push_back(info);
+
 		TreeItem *item = tree->create_item(root);
 		item->set_text(0, name);
 		item->set_editable(0, true);
@@ -340,7 +379,7 @@ void EditorAutoloadSettings::update_autoload() {
 		item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK);
 		item->set_editable(2, true);
 		item->set_text(2, TTR("Enable"));
-		item->set_checked(2, global);
+		item->set_checked(2, info.is_singleton);
 		item->add_button(3, get_icon("FileList", "EditorIcons"), BUTTON_OPEN);
 		item->add_button(3, get_icon("MoveUp", "EditorIcons"), BUTTON_MOVE_UP);
 		item->add_button(3, get_icon("MoveDown", "EditorIcons"), BUTTON_MOVE_DOWN);
@@ -348,6 +387,77 @@ void EditorAutoloadSettings::update_autoload() {
 		item->set_selectable(3, false);
 	}
 
+	// Remove autoload constants
+	for (Map<String, AutoLoadInfo>::Element *E = to_remove_singleton.front(); E; E = E->next()) {
+		for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+			ScriptServer::get_language(i)->remove_named_global_constant(E->get().name);
+		}
+	}
+
+	// Remove obsolete nodes from the tree
+	for (Map<String, AutoLoadInfo>::Element *E = to_remove.front(); E; E = E->next()) {
+		AutoLoadInfo &info = E->get();
+		Node *al = get_node("/root/" + info.name);
+		ERR_CONTINUE(!al);
+		get_tree()->get_root()->remove_child(al);
+		memdelete(al);
+	}
+
+	// Register new singletons already in the tree
+	for (List<String>::Element *E = to_add_singleton.front(); E; E = E->next()) {
+		Node *al = get_node("/root/" + E->get());
+		ERR_CONTINUE(!al);
+		for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+			ScriptServer::get_language(i)->add_named_global_constant(E->get(), al);
+		}
+	}
+
+	// Add new nodes to the tree
+	List<Node *> nodes_to_add;
+	for (List<AutoLoadInfo>::Element *E = to_add.front(); E; E = E->next()) {
+		AutoLoadInfo &info = E->get();
+
+		RES res = ResourceLoader::load(info.path);
+		ERR_EXPLAIN("Can't autoload: " + info.path);
+		ERR_CONTINUE(res.is_null());
+		Node *n = NULL;
+		if (res->is_class("PackedScene")) {
+			Ref<PackedScene> ps = res;
+			n = ps->instance();
+		} else if (res->is_class("Script")) {
+			Ref<Script> s = res;
+			StringName ibt = s->get_instance_base_type();
+			bool valid_type = ClassDB::is_parent_class(ibt, "Node");
+			ERR_EXPLAIN("Script does not inherit a Node: " + info.path);
+			ERR_CONTINUE(!valid_type);
+
+			Object *obj = ClassDB::instance(ibt);
+
+			ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt));
+			ERR_CONTINUE(obj == NULL);
+
+			n = Object::cast_to<Node>(obj);
+			n->set_script(s.get_ref_ptr());
+		}
+
+		ERR_EXPLAIN("Path in autoload not a node or script: " + info.path);
+		ERR_CONTINUE(!n);
+		n->set_name(info.name);
+
+		//defer so references are all valid on _ready()
+		nodes_to_add.push_back(n);
+
+		if (info.is_singleton) {
+			for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+				ScriptServer::get_language(i)->add_named_global_constant(info.name, n);
+			}
+		}
+	}
+
+	for (List<Node *>::Element *E = nodes_to_add.front(); E; E = E->next()) {
+		get_tree()->get_root()->add_child(E->get());
+	}
+
 	updating_autoload = false;
 }
 
@@ -592,6 +702,36 @@ void EditorAutoloadSettings::_bind_methods() {
 
 EditorAutoloadSettings::EditorAutoloadSettings() {
 
+	// Make first cache
+	List<PropertyInfo> props;
+	ProjectSettings::get_singleton()->get_property_list(&props);
+	for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+
+		const PropertyInfo &pi = E->get();
+
+		if (!pi.name.begins_with("autoload/"))
+			continue;
+
+		String name = pi.name.get_slice("/", 1);
+		String path = ProjectSettings::get_singleton()->get(pi.name);
+
+		if (name.empty())
+			continue;
+
+		AutoLoadInfo info;
+		info.is_singleton = path.begins_with("*");
+
+		if (info.is_singleton) {
+			path = path.substr(1, path.length());
+		}
+
+		info.name = name;
+		info.path = path;
+		info.order = ProjectSettings::get_singleton()->get_order(pi.name);
+
+		autoload_cache.push_back(info);
+	}
+
 	autoload_changed = "autoload_changed";
 
 	updating_autoload = false;

+ 2 - 0
editor/editor_autoload_settings.h

@@ -50,6 +50,8 @@ class EditorAutoloadSettings : public VBoxContainer {
 
 	struct AutoLoadInfo {
 		String name;
+		String path;
+		bool is_singleton;
 		int order;
 
 		bool operator==(const AutoLoadInfo &p_info) {

+ 108 - 86
main/main.cpp

@@ -1441,6 +1441,114 @@ bool Main::start() {
 		}
 #endif
 
+		if (!project_manager) { // game or editor
+			if (game_path != "" || script != "") {
+				//autoload
+				List<PropertyInfo> props;
+				ProjectSettings::get_singleton()->get_property_list(&props);
+
+				//first pass, add the constants so they exist before any script is loaded
+				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+
+					String s = E->get().name;
+					if (!s.begins_with("autoload/"))
+						continue;
+					String name = s.get_slicec('/', 1);
+					String path = ProjectSettings::get_singleton()->get(s);
+					bool global_var = false;
+					if (path.begins_with("*")) {
+						global_var = true;
+					}
+
+					if (global_var) {
+						for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+#ifdef TOOLS_ENABLED
+							if (editor) {
+								ScriptServer::get_language(i)->add_named_global_constant(name, Variant());
+							} else {
+								ScriptServer::get_language(i)->add_global_constant(name, Variant());
+							}
+#else
+							ScriptServer::get_language(i)->add_global_constant(name, Variant());
+#endif
+						}
+					}
+				}
+
+				//second pass, load into global constants
+				List<Node *> to_add;
+#ifdef TOOLS_ENABLED
+				ResourceLoader::set_timestamp_on_load(editor); // Avoid problems when editing
+#endif
+				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
+
+					String s = E->get().name;
+					if (!s.begins_with("autoload/"))
+						continue;
+					String name = s.get_slicec('/', 1);
+					String path = ProjectSettings::get_singleton()->get(s);
+					bool global_var = false;
+					if (path.begins_with("*")) {
+						global_var = true;
+						path = path.substr(1, path.length() - 1);
+					}
+
+					RES res = ResourceLoader::load(path);
+					ERR_EXPLAIN("Can't autoload: " + path);
+					ERR_CONTINUE(res.is_null());
+					Node *n = NULL;
+					if (res->is_class("PackedScene")) {
+						Ref<PackedScene> ps = res;
+						n = ps->instance();
+					} else if (res->is_class("Script")) {
+						Ref<Script> s = res;
+						StringName ibt = s->get_instance_base_type();
+						bool valid_type = ClassDB::is_parent_class(ibt, "Node");
+						ERR_EXPLAIN("Script does not inherit a Node: " + path);
+						ERR_CONTINUE(!valid_type);
+
+						Object *obj = ClassDB::instance(ibt);
+
+						ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt));
+						ERR_CONTINUE(obj == NULL);
+
+						n = Object::cast_to<Node>(obj);
+						n->set_script(s.get_ref_ptr());
+					}
+
+					ERR_EXPLAIN("Path in autoload not a node or script: " + path);
+					ERR_CONTINUE(!n);
+					n->set_name(name);
+
+					//defer so references are all valid on _ready()
+					to_add.push_back(n);
+
+					if (global_var) {
+						for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+#ifdef TOOLS_ENABLED
+							if (editor) {
+								ScriptServer::get_language(i)->add_named_global_constant(name, n);
+							} else {
+								ScriptServer::get_language(i)->add_global_constant(name, n);
+							}
+#else
+							ScriptServer::get_language(i)->add_global_constant(name, n);
+#endif
+						}
+					}
+				}
+
+#ifdef TOOLS_ENABLED
+				ResourceLoader::set_timestamp_on_load(false);
+#endif
+
+				for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) {
+
+					sml->get_root()->add_child(E->get());
+				}
+			}
+		}
+
 #ifdef TOOLS_ENABLED
 
 		EditorNode *editor_node = NULL;
@@ -1460,9 +1568,6 @@ bool Main::start() {
 		}
 #endif
 
-		{
-		}
-
 		if (!editor && !project_manager) {
 			//standard helpers that can be changed from main config
 
@@ -1575,89 +1680,6 @@ bool Main::start() {
 		}
 
 		if (!project_manager && !editor) { // game
-			if (game_path != "" || script != "") {
-				//autoload
-				List<PropertyInfo> props;
-				ProjectSettings::get_singleton()->get_property_list(&props);
-
-				//first pass, add the constants so they exist before any script is loaded
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
-					String s = E->get().name;
-					if (!s.begins_with("autoload/"))
-						continue;
-					String name = s.get_slicec('/', 1);
-					String path = ProjectSettings::get_singleton()->get(s);
-					bool global_var = false;
-					if (path.begins_with("*")) {
-						global_var = true;
-					}
-
-					if (global_var) {
-						for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-							ScriptServer::get_language(i)->add_global_constant(name, Variant());
-						}
-					}
-				}
-
-				//second pass, load into global constants
-				List<Node *> to_add;
-				for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) {
-
-					String s = E->get().name;
-					if (!s.begins_with("autoload/"))
-						continue;
-					String name = s.get_slicec('/', 1);
-					String path = ProjectSettings::get_singleton()->get(s);
-					bool global_var = false;
-					if (path.begins_with("*")) {
-						global_var = true;
-						path = path.substr(1, path.length() - 1);
-					}
-
-					RES res = ResourceLoader::load(path);
-					ERR_EXPLAIN("Can't autoload: " + path);
-					ERR_CONTINUE(res.is_null());
-					Node *n = NULL;
-					if (res->is_class("PackedScene")) {
-						Ref<PackedScene> ps = res;
-						n = ps->instance();
-					} else if (res->is_class("Script")) {
-						Ref<Script> s = res;
-						StringName ibt = s->get_instance_base_type();
-						bool valid_type = ClassDB::is_parent_class(ibt, "Node");
-						ERR_EXPLAIN("Script does not inherit a Node: " + path);
-						ERR_CONTINUE(!valid_type);
-
-						Object *obj = ClassDB::instance(ibt);
-
-						ERR_EXPLAIN("Cannot instance script for autoload, expected 'Node' inheritance, got: " + String(ibt));
-						ERR_CONTINUE(obj == NULL);
-
-						n = Object::cast_to<Node>(obj);
-						n->set_script(s.get_ref_ptr());
-					}
-
-					ERR_EXPLAIN("Path in autoload not a node or script: " + path);
-					ERR_CONTINUE(!n);
-					n->set_name(name);
-
-					//defer so references are all valid on _ready()
-					to_add.push_back(n);
-
-					if (global_var) {
-						for (int i = 0; i < ScriptServer::get_language_count(); i++) {
-							ScriptServer::get_language(i)->add_global_constant(name, n);
-						}
-					}
-				}
-
-				for (List<Node *>::Element *E = to_add.front(); E; E = E->next()) {
-
-					sml->get_root()->add_child(E->get());
-				}
-			}
-
 			if (game_path != "") {
 				Node *scene = NULL;
 				Ref<PackedScene> scenedata = ResourceLoader::load(local_game_path);

+ 9 - 0
modules/gdscript/gdscript.cpp

@@ -1333,6 +1333,15 @@ void GDScriptLanguage::add_global_constant(const StringName &p_variable, const V
 	_add_global(p_variable, p_value);
 }
 
+void GDScriptLanguage::add_named_global_constant(const StringName &p_name, const Variant &p_value) {
+	named_globals[p_name] = p_value;
+}
+
+void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) {
+	ERR_FAIL_COND(!named_globals.has(p_name));
+	named_globals.erase(p_name);
+}
+
 void GDScriptLanguage::init() {
 
 	//populate global constants

+ 5 - 1
modules/gdscript/gdscript.h

@@ -262,6 +262,7 @@ class GDScriptLanguage : public ScriptLanguage {
 	Variant *_global_array;
 	Vector<Variant> global_array;
 	Map<StringName, int> globals;
+	Map<StringName, Variant> named_globals;
 
 	struct CallLevel {
 
@@ -369,7 +370,8 @@ public:
 
 	_FORCE_INLINE_ int get_global_array_size() const { return global_array.size(); }
 	_FORCE_INLINE_ Variant *get_global_array() { return _global_array; }
-	_FORCE_INLINE_ const Map<StringName, int> &get_global_map() { return globals; }
+	_FORCE_INLINE_ const Map<StringName, int> &get_global_map() const { return globals; }
+	_FORCE_INLINE_ const Map<StringName, Variant> &get_named_globals_map() const { return named_globals; }
 
 	_FORCE_INLINE_ static GDScriptLanguage *get_singleton() { return singleton; }
 
@@ -403,6 +405,8 @@ public:
 	virtual String _get_indentation() const;
 	virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const;
 	virtual void add_global_constant(const StringName &p_variable, const Variant &p_value);
+	virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value);
+	virtual void remove_named_global_constant(const StringName &p_name);
 
 	/* DEBUGGER FUNCTIONS */
 

+ 24 - 0
modules/gdscript/gdscript_compiler.cpp

@@ -278,6 +278,18 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
 				return idx | (GDScriptFunction::ADDR_TYPE_GLOBAL << GDScriptFunction::ADDR_BITS); //argument (stack root)
 			}
 
+#ifdef TOOLS_ENABLED
+			if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(identifier)) {
+
+				int idx = codegen.named_globals.find(identifier);
+				if (idx == -1) {
+					idx = codegen.named_globals.size();
+					codegen.named_globals.push_back(identifier);
+				}
+				return idx | (GDScriptFunction::ADDR_TYPE_NAMED_GLOBAL << GDScriptFunction::ADDR_BITS);
+			}
+#endif
+
 			//not found, error
 
 			_set_error("Identifier not found: " + String(identifier), p_expression);
@@ -1511,6 +1523,18 @@ Error GDScriptCompiler::_parse_function(GDScript *p_script, const GDScriptParser
 		gdfunc->_global_names_count = 0;
 	}
 
+#ifdef TOOLS_ENABLED
+	// Named globals
+	if (codegen.named_globals.size()) {
+		gdfunc->named_globals.resize(codegen.named_globals.size());
+		gdfunc->_named_globals_ptr = gdfunc->named_globals.ptr();
+		for (int i = 0; i < codegen.named_globals.size(); i++) {
+			gdfunc->named_globals[i] = codegen.named_globals[i];
+		}
+		gdfunc->_named_globals_count = gdfunc->named_globals.size();
+	}
+#endif
+
 	if (codegen.opcodes.size()) {
 
 		gdfunc->code = codegen.opcodes;

+ 3 - 0
modules/gdscript/gdscript_compiler.h

@@ -94,6 +94,9 @@ class GDScriptCompiler {
 
 		HashMap<Variant, int, VariantHasher, VariantComparator> constant_map;
 		Map<StringName, int> name_map;
+#ifdef TOOLS_ENABLED
+		Vector<StringName> named_globals;
+#endif
 
 		int get_name_map_pos(const StringName &p_identifier) {
 			int ret;

+ 15 - 0
modules/gdscript/gdscript_function.cpp

@@ -108,6 +108,21 @@ Variant *GDScriptFunction::_get_variant(int p_address, GDScriptInstance *p_insta
 #endif
 			return &GDScriptLanguage::get_singleton()->get_global_array()[address];
 		} break;
+#ifdef TOOLS_ENABLED
+		case ADDR_TYPE_NAMED_GLOBAL: {
+#ifdef DEBUG_ENABLED
+			ERR_FAIL_INDEX_V(address, _named_globals_count, NULL);
+#endif
+			StringName id = _named_globals_ptr[address];
+
+			if (GDScriptLanguage::get_singleton()->get_named_globals_map().has(id)) {
+				return (Variant *)&GDScriptLanguage::get_singleton()->get_named_globals_map()[id];
+			} else {
+				r_error = "Autoload singleton '" + String(id) + "' has been removed.";
+				return NULL;
+			}
+		} break;
+#endif
 		case ADDR_TYPE_NIL: {
 			return &nil;
 		} break;

+ 9 - 1
modules/gdscript/gdscript_function.h

@@ -92,7 +92,8 @@ public:
 		ADDR_TYPE_STACK = 5,
 		ADDR_TYPE_STACK_VARIABLE = 6,
 		ADDR_TYPE_GLOBAL = 7,
-		ADDR_TYPE_NIL = 8
+		ADDR_TYPE_NAMED_GLOBAL = 8,
+		ADDR_TYPE_NIL = 9
 	};
 
 	enum RPCMode {
@@ -121,6 +122,10 @@ private:
 	int _constant_count;
 	const StringName *_global_names_ptr;
 	int _global_names_count;
+#ifdef TOOLS_ENABLED
+	const StringName *_named_globals_ptr;
+	int _named_globals_count;
+#endif
 	const int *_default_arg_ptr;
 	int _default_arg_count;
 	const int *_code_ptr;
@@ -137,6 +142,9 @@ private:
 	StringName name;
 	Vector<Variant> constants;
 	Vector<StringName> global_names;
+#ifdef TOOLS_ENABLED
+	Vector<StringName> named_globals;
+#endif
 	Vector<int> default_arguments;
 	Vector<int> code;