Browse Source

added ability to define signals in script

closes #2175
Juan Linietsky 10 years ago
parent
commit
48f1d02da4

+ 9 - 0
core/object.cpp

@@ -1302,6 +1302,10 @@ Array Object::_get_signal_connection_list(const String& p_signal) const{
 
 
 void Object::get_signal_list(List<MethodInfo> *p_signals ) const {
 void Object::get_signal_list(List<MethodInfo> *p_signals ) const {
 
 
+	if (!script.is_null()) {
+		Ref<Script>(script)->get_script_signal_list(p_signals);
+	}
+
 	ObjectTypeDB::get_signal_list(get_type_name(),p_signals);
 	ObjectTypeDB::get_signal_list(get_type_name(),p_signals);
 	//find maybe usersignals?
 	//find maybe usersignals?
 	const StringName *S=NULL;
 	const StringName *S=NULL;
@@ -1313,6 +1317,7 @@ void Object::get_signal_list(List<MethodInfo> *p_signals ) const {
 			p_signals->push_back(signal_map[*S].user);
 			p_signals->push_back(signal_map[*S].user);
 		}
 		}
 	}
 	}
+
 }
 }
 
 
 
 
@@ -1351,6 +1356,10 @@ Error Object::connect(const StringName& p_signal, Object *p_to_object, const Str
 	Signal *s = signal_map.getptr(p_signal);
 	Signal *s = signal_map.getptr(p_signal);
 	if (!s) {
 	if (!s) {
 		bool signal_is_valid = ObjectTypeDB::has_signal(get_type_name(),p_signal);
 		bool signal_is_valid = ObjectTypeDB::has_signal(get_type_name(),p_signal);
+		//check in script
+		if (!signal_is_valid && !script.is_null() && Ref<Script>(script)->has_script_signal(p_signal))
+			signal_is_valid=true;
+
 		if (!signal_is_valid) {
 		if (!signal_is_valid) {
 			ERR_EXPLAIN("Attempt to connect nonexistent signal '"+p_signal+"' to method '"+p_to_method+"'");
 			ERR_EXPLAIN("Attempt to connect nonexistent signal '"+p_signal+"' to method '"+p_to_method+"'");
 			ERR_FAIL_COND_V(!signal_is_valid,ERR_INVALID_PARAMETER);
 			ERR_FAIL_COND_V(!signal_is_valid,ERR_INVALID_PARAMETER);

+ 4 - 0
core/script_language.h

@@ -94,6 +94,10 @@ public:
 
 
 	virtual ScriptLanguage *get_language() const=0;
 	virtual ScriptLanguage *get_language() const=0;
 
 
+	virtual bool has_script_signal(const StringName& p_signal) const=0;
+	virtual void get_script_signal_list(List<MethodInfo> *r_signals) const=0;
+
+
 	virtual void update_exports() {} //editor tool
 	virtual void update_exports() {} //editor tool
 
 
 	
 	

+ 1 - 0
demos/2d/space_shooter/shot.gd

@@ -45,3 +45,4 @@ func _on_shot_body_enter( body ):
 	#hit the tilemap
 	#hit the tilemap
 	_hit_something()
 	_hit_something()
 	pass # replace with function body
 	pass # replace with function body
+

BIN
demos/2d/space_shooter/shot.scn


+ 30 - 10
modules/gdscript/gd_compiler.cpp

@@ -28,15 +28,6 @@
 /*************************************************************************/
 /*************************************************************************/
 #include "gd_compiler.h"
 #include "gd_compiler.h"
 #include "gd_script.h"
 #include "gd_script.h"
-/* TODO:
-
-   *AND and OR need early abort
-   -Inheritance properly process (done?)
-   *create built in initializer and constructor
-   *assign operators
-   *build arrays and dictionaries
-   *call parent constructor
- */
 
 
 
 
 void GDCompiler::_set_error(const String& p_error,const GDParser::Node *p_node) {
 void GDCompiler::_set_error(const String& p_error,const GDParser::Node *p_node) {
@@ -1397,13 +1388,14 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars
 
 
 
 
 	int index_from=0;
 	int index_from=0;
+	Ref<GDNativeClass> native;
 
 
 	if (p_class->extends_used) {
 	if (p_class->extends_used) {
 		//do inheritance
 		//do inheritance
 		String path = p_class->extends_file;
 		String path = p_class->extends_file;
 
 
 		Ref<GDScript> script;
 		Ref<GDScript> script;
-		Ref<GDNativeClass> native;
+
 
 
 		if (path!="") {
 		if (path!="") {
 			//path (and optionally subclasses)
 			//path (and optionally subclasses)
@@ -1573,7 +1565,35 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars
 		//p_script->constants[constant->value].make_const();
 		//p_script->constants[constant->value].make_const();
 	}
 	}
 
 
+	for(int i=0;i<p_class->_signals.size();i++) {
+
+		StringName name = p_class->_signals[i].name;
+
+		GDScript *c = p_script;
 
 
+		while(c) {
+
+			if (c->_signals.has(name)) {
+				_set_error("Signal '"+name+"' redefined (in current or parent class)",p_class);
+				return ERR_ALREADY_EXISTS;
+			}
+
+			if (c->base.is_valid()) {
+				c=c->base.ptr();
+			} else {
+				c=NULL;
+			}
+		}
+
+		if (native.is_valid()) {
+			if (ObjectTypeDB::has_signal(native->get_name(),name)) {
+				_set_error("Signal '"+name+"' redefined (original in native class '"+String(native->get_name())+"')",p_class);
+				return ERR_ALREADY_EXISTS;
+			}
+		}
+
+		p_script->_signals[name]=p_class->_signals[i].arguments;
+	}
 	//parse sub-classes
 	//parse sub-classes
 
 
 	for(int i=0;i<p_class->subclasses.size();i++) {
 	for(int i=0;i<p_class->subclasses.size();i++) {

+ 63 - 8
modules/gdscript/gd_parser.cpp

@@ -1520,8 +1520,10 @@ void GDParser::_parse_block(BlockNode *p_block,bool p_static) {
 				op->arguments.push_back(assigned);
 				op->arguments.push_back(assigned);
 				p_block->statements.push_back(op);
 				p_block->statements.push_back(op);
 
 
-				_end_statement();
-
+				if (!_end_statement()) {
+					_set_error("Expected end of statement (var)");
+					return;
+				}
 
 
 			} break;
 			} break;
 			case GDTokenizer::TK_CF_IF: {
 			case GDTokenizer::TK_CF_IF: {
@@ -1946,8 +1948,10 @@ void GDParser::_parse_class(ClassNode *p_class) {
 				_parse_extends(p_class);
 				_parse_extends(p_class);
 				if (error_set)
 				if (error_set)
 					return;
 					return;
-				_end_statement();
-
+				if (!_end_statement()) {
+					_set_error("Expected end of statement after extends");
+					return;
+				}
 
 
 			} break;
 			} break;
 			case GDTokenizer::TK_PR_TOOL: {
 			case GDTokenizer::TK_PR_TOOL: {
@@ -2227,6 +2231,53 @@ void GDParser::_parse_class(ClassNode *p_class) {
 
 
 				//arguments
 				//arguments
 			} break;
 			} break;
+			case GDTokenizer::TK_PR_SIGNAL: {
+				tokenizer->advance();
+
+				if (tokenizer->get_token()!=GDTokenizer::TK_IDENTIFIER) {
+					_set_error("Expected identifier after 'signal'.");
+					return;
+				}
+
+				ClassNode::Signal sig;
+				sig.name = tokenizer->get_token_identifier();
+				tokenizer->advance();
+
+
+				if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_OPEN) {
+					tokenizer->advance();
+					while(true) {
+
+
+						if (tokenizer->get_token()==GDTokenizer::TK_PARENTHESIS_CLOSE) {
+							tokenizer->advance();
+							break;
+						}
+
+						if (tokenizer->get_token()!=GDTokenizer::TK_IDENTIFIER) {
+							_set_error("Expected identifier in signal argument.");
+							return;
+						}
+
+						sig.arguments.push_back(tokenizer->get_token_identifier());
+						tokenizer->advance();
+
+						if (tokenizer->get_token()==GDTokenizer::TK_COMMA) {
+							tokenizer->advance();
+						} else if (tokenizer->get_token()!=GDTokenizer::TK_PARENTHESIS_CLOSE) {
+							_set_error("Expected ',' or ')' after signal parameter identifier.");
+							return;
+						}
+					}
+				}
+
+				p_class->_signals.push_back(sig);
+
+				if (!_end_statement()) {
+					_set_error("Expected end of statement (signal)");
+					return;
+				}
+			} break;
 			case GDTokenizer::TK_PR_EXPORT: {
 			case GDTokenizer::TK_PR_EXPORT: {
 
 
 				tokenizer->advance();
 				tokenizer->advance();
@@ -2644,8 +2695,10 @@ void GDParser::_parse_class(ClassNode *p_class) {
 
 
 				p_class->variables.push_back(member);
 				p_class->variables.push_back(member);
 
 
-				_end_statement();
-
+				if (!_end_statement()) {
+					_set_error("Expected end of statement (continue)");
+					return;
+				}
 			} break;
 			} break;
 			case GDTokenizer::TK_PR_CONST: {
 			case GDTokenizer::TK_PR_CONST: {
 				//variale declaration and (eventual) initialization
 				//variale declaration and (eventual) initialization
@@ -2682,8 +2735,10 @@ void GDParser::_parse_class(ClassNode *p_class) {
 
 
 				p_class->constant_expressions.push_back(constant);
 				p_class->constant_expressions.push_back(constant);
 
 
-				_end_statement();
-
+				if (!_end_statement()) {
+					_set_error("Expected end of statement (constant)");
+					return;
+				}
 
 
 			} break;
 			} break;
 
 

+ 7 - 0
modules/gdscript/gd_parser.h

@@ -76,6 +76,7 @@ public:
 		StringName extends_file;
 		StringName extends_file;
 		Vector<StringName> extends_class;
 		Vector<StringName> extends_class;
 
 
+
 		struct Member {
 		struct Member {
 			PropertyInfo _export;
 			PropertyInfo _export;
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
@@ -92,11 +93,17 @@ public:
 			Node *expression;
 			Node *expression;
 		};
 		};
 
 
+		struct Signal {
+			StringName name;
+			Vector<StringName> arguments;
+		};
+
 		Vector<ClassNode*> subclasses;
 		Vector<ClassNode*> subclasses;
 		Vector<Member> variables;
 		Vector<Member> variables;
 		Vector<Constant> constant_expressions;
 		Vector<Constant> constant_expressions;
 		Vector<FunctionNode*> functions;
 		Vector<FunctionNode*> functions;
 		Vector<FunctionNode*> static_functions;
 		Vector<FunctionNode*> static_functions;
+		Vector<Signal> _signals;
 		BlockNode *initializer;
 		BlockNode *initializer;
 		ClassNode *owner;
 		ClassNode *owner;
 		//Vector<Node*> initializers;
 		//Vector<Node*> initializers;

+ 48 - 0
modules/gdscript/gd_script.cpp

@@ -1756,6 +1756,12 @@ bool GDScript::_update_exports() {
 				//print_line("found "+c->variables[i]._export.name);
 				//print_line("found "+c->variables[i]._export.name);
 				member_default_values_cache[c->variables[i].identifier]=c->variables[i].default_value;
 				member_default_values_cache[c->variables[i].identifier]=c->variables[i].default_value;
 			}
 			}
+
+			_signals.clear();
+
+			for(int i=0;i<c->_signals.size();i++) {
+				_signals[c->_signals[i].name]=c->_signals[i].arguments;
+			}
 		}
 		}
 	} else {
 	} else {
 		//print_line("unchaged is "+get_path());
 		//print_line("unchaged is "+get_path());
@@ -2100,6 +2106,47 @@ Ref<GDScript> GDScript::get_base() const {
 	return base;
 	return base;
 }
 }
 
 
+bool GDScript::has_script_signal(const StringName& p_signal) const {
+	if (_signals.has(p_signal))
+		return true;
+	if (base.is_valid()) {
+		return base->has_script_signal(p_signal);
+	}
+#ifdef TOOLS_ENABLED
+	else if (base_cache.is_valid()){
+		return base_cache->has_script_signal(p_signal);
+	}
+
+#endif
+	return false;
+}
+void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
+
+	for(const Map<StringName,Vector<StringName> >::Element *E=_signals.front();E;E=E->next()) {
+
+		MethodInfo mi;
+		mi.name=E->key();
+		for(int i=0;i<E->get().size();i++) {
+			PropertyInfo arg;
+			arg.name=E->get()[i];
+			mi.arguments.push_back(arg);
+		}
+		r_signals->push_back(mi);
+	}
+
+	if (base.is_valid()) {
+		base->get_script_signal_list(r_signals);
+	}
+#ifdef TOOLS_ENABLED
+	else if (base_cache.is_valid()){
+		base_cache->get_script_signal_list(r_signals);
+	}
+
+#endif
+
+}
+
+
 GDScript::GDScript() {
 GDScript::GDScript() {
 
 
 
 
@@ -2594,6 +2641,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const  {
 		"static",
 		"static",
 		"float",
 		"float",
 		"int",
 		"int",
+		"signal",
 	0};
 	0};
 
 
 
 

+ 4 - 0
modules/gdscript/gd_script.h

@@ -260,6 +260,7 @@ friend class GDScriptLanguage;
 	Map<StringName,GDFunction> member_functions;
 	Map<StringName,GDFunction> member_functions;
 	Map<StringName,MemberInfo> member_indices; //members are just indices to the instanced script.
 	Map<StringName,MemberInfo> member_indices; //members are just indices to the instanced script.
 	Map<StringName,Ref<GDScript> > subclasses;	
 	Map<StringName,Ref<GDScript> > subclasses;	
+	Map<StringName,Vector<StringName> > _signals;
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 
 
@@ -318,6 +319,9 @@ public:
 	const Map<StringName,GDFunction>& get_member_functions() const { return member_functions; }
 	const Map<StringName,GDFunction>& get_member_functions() const { return member_functions; }
 	const Ref<GDNativeClass>& get_native() const { return native; }
 	const Ref<GDNativeClass>& get_native() const { return native; }
 
 
+	virtual bool has_script_signal(const StringName& p_signal) const;
+	virtual void get_script_signal_list(List<MethodInfo> *r_signals) const;
+
 
 
 	bool is_tool() const { return tool; }
 	bool is_tool() const { return tool; }
 	Ref<GDScript> get_base() const;
 	Ref<GDScript> get_base() const;

+ 1 - 0
modules/gdscript/gd_tokenizer.cpp

@@ -856,6 +856,7 @@ void GDTokenizerText::_advance() {
 								{TK_PR_PRELOAD,"preload"},
 								{TK_PR_PRELOAD,"preload"},
 								{TK_PR_ASSERT,"assert"},
 								{TK_PR_ASSERT,"assert"},
 								{TK_PR_YIELD,"yield"},
 								{TK_PR_YIELD,"yield"},
+								{TK_PR_SIGNAL,"signal"},
 								{TK_PR_CONST,"const"},
 								{TK_PR_CONST,"const"},
 								//controlflow
 								//controlflow
 								{TK_CF_IF,"if"},
 								{TK_CF_IF,"if"},

+ 1 - 0
modules/gdscript/gd_tokenizer.h

@@ -104,6 +104,7 @@ public:
 		TK_PR_PRELOAD,
 		TK_PR_PRELOAD,
 		TK_PR_ASSERT,
 		TK_PR_ASSERT,
 		TK_PR_YIELD,
 		TK_PR_YIELD,
+		TK_PR_SIGNAL,
 		TK_BRACKET_OPEN,
 		TK_BRACKET_OPEN,
 		TK_BRACKET_CLOSE,
 		TK_BRACKET_CLOSE,
 		TK_CURLY_BRACKET_OPEN,
 		TK_CURLY_BRACKET_OPEN,

+ 102 - 46
tools/editor/connections_dialog.cpp

@@ -632,74 +632,130 @@ void ConnectionsDialog::update_tree() {
 	node->get_signal_list(&node_signals);
 	node->get_signal_list(&node_signals);
 
 
 	//node_signals.sort_custom<_ConnectionsDialogMethodInfoSort>();
 	//node_signals.sort_custom<_ConnectionsDialogMethodInfoSort>();
+	bool did_script=false;
+	StringName base = node->get_type();
 
 
-	for(List<MethodInfo>::Element *E=node_signals.front();E;E=E->next()) {
-		
+	while(base) {
+
+		List<MethodInfo> node_signals;
+		Ref<Texture> icon;
+		String name;
 
 
-		MethodInfo &mi =E->get();
+		if (!did_script) {
 
 
-		String signaldesc;
-		signaldesc=mi.name+"(";
-		StringArray argnames;
-		if (mi.arguments.size()) {
-			signaldesc+=" ";
-			for(int i=0;i<mi.arguments.size();i++) {
+			Ref<Script> scr = node->get_script();
+			if (scr.is_valid()) {
+				scr->get_script_signal_list(&node_signals);
+				if (scr->get_path().is_resource_file())
+					name=scr->get_path().get_file();
+				else
+					name=scr->get_type();
 
 
-				PropertyInfo &pi = mi.arguments[i];
+				if (has_icon(scr->get_type(),"EditorIcons")) {
+					icon=get_icon(scr->get_type(),"EditorIcons");
+				}
+			}
 
 
-				if (i>0)
-					signaldesc+=", ";
-				signaldesc+=Variant::get_type_name(pi.type)+" "+(pi.name==""?String("arg "+itos(i)):pi.name);
-				argnames.push_back(pi.name);
+		} else {
 
 
+			ObjectTypeDB::get_signal_list(base,&node_signals,true);
+			if (has_icon(base,"EditorIcons")) {
+				icon=get_icon(base,"EditorIcons");
 			}
 			}
-			signaldesc+=" ";
+			name=base;
 		}
 		}
 
 
-		signaldesc+=")";
-		
-		TreeItem *item=tree->create_item(root);
-		item->set_text(0,signaldesc);
-		Dictionary sinfo;
-		sinfo["name"]=mi.name;
-		sinfo["args"]=argnames;
-		item->set_metadata(0,sinfo);
-		item->set_icon(0,get_icon("Signal","EditorIcons"));
 
 
-		List<Object::Connection> connections;
-		node->get_signal_connection_list(mi.name,&connections);
+		TreeItem *pitem = NULL;
+
+		if (node_signals.size()) {
+			pitem=tree->create_item(root);
+			pitem->set_text(0,name);
+			pitem->set_icon(0,icon);
+			pitem->set_selectable(0,false);
+			pitem->set_editable(0,false);
+			pitem->set_custom_bg_color(0,get_color("prop_subsection","Editor"));
+			node_signals.sort();
+		}
 
 
-		for(List<Object::Connection>::Element *F=connections.front();F;F=F->next()) {
+		for(List<MethodInfo>::Element *E=node_signals.front();E;E=E->next()) {
 
 
-			Object::Connection&c = F->get();
-			if (!(c.flags&CONNECT_PERSIST))
-				continue;
 
 
-			Node *target = c.target->cast_to<Node>();
-			if (!target)
-				continue;
+			MethodInfo &mi =E->get();
 
 
-			String path = String(node->get_path_to(target))+" :: "+c.method+"()";
-			if (c.flags&CONNECT_DEFERRED)
-				path+=" (deferred)";
-			if (c.binds.size()) {
+			String signaldesc;
+			signaldesc=mi.name+"(";
+			StringArray argnames;
+			if (mi.arguments.size()) {
+				signaldesc+=" ";
+				for(int i=0;i<mi.arguments.size();i++) {
 
 
-				path+=" binds( ";
-				for(int i=0;i<c.binds.size();i++) {
+					PropertyInfo &pi = mi.arguments[i];
 
 
 					if (i>0)
 					if (i>0)
-						path+=", ";
-					path+=c.binds[i].operator String();
+						signaldesc+=", ";
+					String tname="var";
+					if (pi.type!=Variant::NIL) {
+						tname=Variant::get_type_name(pi.type);
+					}
+					signaldesc+=tname+" "+(pi.name==""?String("arg "+itos(i)):pi.name);
+					argnames.push_back(pi.name);
+
 				}
 				}
-				path+=" )";
+				signaldesc+=" ";
 			}
 			}
 
 
-			TreeItem *item2=tree->create_item(item);
-			item2->set_text(0,path);
-			item2->set_metadata(0,c);
-			item2->set_icon(0,get_icon("Slot","EditorIcons"));
+			signaldesc+=")";
+
+			TreeItem *item=tree->create_item(pitem);
+			item->set_text(0,signaldesc);
+			Dictionary sinfo;
+			sinfo["name"]=mi.name;
+			sinfo["args"]=argnames;
+			item->set_metadata(0,sinfo);
+			item->set_icon(0,get_icon("Signal","EditorIcons"));
+
+			List<Object::Connection> connections;
+			node->get_signal_connection_list(mi.name,&connections);
 
 
+			for(List<Object::Connection>::Element *F=connections.front();F;F=F->next()) {
+
+				Object::Connection&c = F->get();
+				if (!(c.flags&CONNECT_PERSIST))
+					continue;
+
+				Node *target = c.target->cast_to<Node>();
+				if (!target)
+					continue;
+
+				String path = String(node->get_path_to(target))+" :: "+c.method+"()";
+				if (c.flags&CONNECT_DEFERRED)
+					path+=" (deferred)";
+				if (c.binds.size()) {
+
+					path+=" binds( ";
+					for(int i=0;i<c.binds.size();i++) {
+
+						if (i>0)
+							path+=", ";
+						path+=c.binds[i].operator String();
+					}
+					path+=" )";
+				}
+
+				TreeItem *item2=tree->create_item(item);
+				item2->set_text(0,path);
+				item2->set_metadata(0,c);
+				item2->set_icon(0,get_icon("Slot","EditorIcons"));
+
+
+			}
+		}
 
 
+		if (!did_script) {
+			did_script=true;
+		} else {
+			base=ObjectTypeDB::type_inherits_from(base);
 		}
 		}
 	}
 	}