Browse Source

Ability to reload scripts on running game

Juan Linietsky 9 years ago
parent
commit
9e745b920f

+ 13 - 1
core/script_debugger_remote.cpp

@@ -291,6 +291,8 @@ void ScriptDebuggerRemote::debug(ScriptLanguage *p_script,bool p_can_continue) {
 
 				_set_object_property(cmd[1],cmd[2],cmd[3]);
 
+			} else if (command=="reload_scripts") {
+				reload_all_scripts=true;
 			} else if (command=="breakpoint") {
 
 				bool set = cmd[3];
@@ -698,7 +700,8 @@ void ScriptDebuggerRemote::_poll_events() {
 			profiling=false;
 			_send_profiling_data(false);
 			print_line("PROFILING END!");
-
+		} else if (command=="reload_scripts") {
+			reload_all_scripts=true;
 		} else if (command=="breakpoint") {
 
 			bool set = cmd[3];
@@ -863,6 +866,14 @@ void ScriptDebuggerRemote::idle_poll() {
 		    }
 	    }
 
+	    if (reload_all_scripts) {
+
+		    for(int i=0;i<ScriptServer::get_language_count();i++) {
+			    ScriptServer::get_language(i)->reload_all_scripts();
+		    }
+		    reload_all_scripts=false;
+	    }
+
 	    _poll_events();
 
 }
@@ -1012,6 +1023,7 @@ ScriptDebuggerRemote::ScriptDebuggerRemote() {
 	profile_info_ptrs.resize(profile_info.size());
 	profiling=false;
 	max_frame_functions=16;
+	reload_all_scripts=false;
 
 }
 

+ 2 - 0
core/script_debugger_remote.h

@@ -60,6 +60,7 @@ class ScriptDebuggerRemote : public ScriptDebugger {
 	bool profiling;
 	int max_frame_functions;
 	bool skip_profile_frame;
+	bool reload_all_scripts;
 
 
 	Ref<StreamPeerTCP> tcp_client;
@@ -168,6 +169,7 @@ public:
 	virtual void profiling_end();
 	virtual void profiling_set_frame_times(float p_frame_time,float p_idle_time,float p_fixed_time,float p_fixed_frame_time);
 
+
 	ScriptDebuggerRemote();
 	~ScriptDebuggerRemote();
 };

+ 1 - 1
core/script_language.cpp

@@ -51,7 +51,7 @@ void Script::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("has_source_code"),&Script::has_source_code);
 	ObjectTypeDB::bind_method(_MD("get_source_code"),&Script::get_source_code);
 	ObjectTypeDB::bind_method(_MD("set_source_code","source"),&Script::set_source_code);
-	ObjectTypeDB::bind_method(_MD("reload"),&Script::reload);
+	ObjectTypeDB::bind_method(_MD("reload","keep_state"),&Script::reload,DEFVAL(false));
 
 }
 

+ 6 - 1
core/script_language.h

@@ -87,7 +87,7 @@ public:
 	virtual bool has_source_code() const=0;
 	virtual String get_source_code() const=0;
 	virtual void set_source_code(const String& p_code)=0;
-	virtual Error reload()=0;
+	virtual Error reload(bool p_keep_state=false)=0;
 
 	virtual bool is_tool() const=0;
 
@@ -127,6 +127,8 @@ public:
 
 	virtual Ref<Script> get_script() const=0;
 
+	virtual bool is_placeholder() const { return false; }
+
 	virtual ScriptLanguage *get_language()=0;
 	virtual ~ScriptInstance();
 };
@@ -189,6 +191,7 @@ public:
 
 	virtual Vector<StackInfo> debug_get_current_stack_info() { return Vector<StackInfo>(); }
 
+	virtual void reload_all_scripts()=0;
 	/* LOADER FUNCTIONS */
 
 	virtual void get_recognized_extensions(List<String> *p_extensions) const=0;
@@ -248,6 +251,8 @@ public:
 
 	void update(const List<PropertyInfo> &p_properties,const Map<StringName,Variant>& p_values); //likely changed in editor
 
+	virtual bool is_placeholder() const { return true; }
+
 	PlaceHolderScriptInstance(ScriptLanguage *p_language, Ref<Script> p_script,Object *p_owner);
 	~PlaceHolderScriptInstance();
 

+ 69 - 7
modules/gdscript/gd_compiler.cpp

@@ -1414,8 +1414,13 @@ Error GDCompiler::_parse_function(GDScript *p_script,const GDParser::ClassNode *
 
 
 
-Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class) {
+Error GDCompiler::_parse_class(GDScript *p_script, GDScript *p_owner, const GDParser::ClassNode *p_class, bool p_keep_state) {
 
+	Map<StringName,Ref<GDScript> > old_subclasses;
+
+	if (p_keep_state) {
+		old_subclasses=p_script->subclasses;
+	}
 
 	p_script->native=Ref<GDNativeClass>();
 	p_script->base=Ref<GDScript>();
@@ -1429,6 +1434,7 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars
 	p_script->member_indices.clear();
 	p_script->member_info.clear();
 	p_script->initializer=NULL;
+
 	p_script->subclasses.clear();
 	p_script->_owner=p_owner;
 	p_script->tool=p_class->tool;
@@ -1662,9 +1668,15 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars
 	for(int i=0;i<p_class->subclasses.size();i++) {
 		StringName name = p_class->subclasses[i]->name;
 
-		Ref<GDScript> subclass = memnew( GDScript );
+		Ref<GDScript> subclass;
+
+		if (old_subclasses.has(name)) {
+			subclass=old_subclasses[name];
+		} else {
+			subclass.instance();
+		}
 
-		Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i]);
+		Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i],p_keep_state);
 		if (err)
 			return err;
 
@@ -1755,13 +1767,65 @@ Error GDCompiler::_parse_class(GDScript *p_script,GDScript *p_owner,const GDPars
 
 		}
 	}
+
+	//validate instances if keeping state
+
+	if (p_keep_state) {
+
+		print_line("RELOAD KEEP "+p_script->path);
+		for (Set<Object*>::Element *E=p_script->instances.front();E;) {
+
+			Set<Object*>::Element *N = E->next();
+
+			ScriptInstance *si = E->get()->get_script_instance();
+			if (si->is_placeholder()) {
+				PlaceHolderScriptInstance *psi = static_cast<PlaceHolderScriptInstance*>(si);
+
+				if (p_script->is_tool()) {
+					//re-create as an instance
+					p_script->placeholders.erase(psi); //remove placeholder
+
+					GDInstance* instance = memnew( GDInstance );
+					instance->base_ref=E->get()->cast_to<Reference>();
+					instance->members.resize(p_script->member_indices.size());
+					instance->script=Ref<GDScript>(p_script);
+					instance->owner=E->get();
+
+					//needed for hot reloading
+					for(Map<StringName,GDScript::MemberInfo>::Element *E=p_script->member_indices.front();E;E=E->next()) {
+						instance->member_indices_cache[E->key()]=E->get().index;
+					}
+					instance->owner->set_script_instance(instance);
+
+
+					/* STEP 2, INITIALIZE AND CONSRTUCT */
+
+					Variant::CallError ce;
+					p_script->initializer->call(instance,NULL,0,ce);
+
+					if (ce.error!=Variant::CallError::CALL_OK) {
+						//well, tough luck, not goinna do anything here
+					}
+				}
+			} else {
+				print_line("RELOAD MEMBERS");
+				GDInstance *gi = static_cast<GDInstance*>(si);
+				gi->reload_members();
+			}
+
+			E=N;
+
+		}
+
+
+	}
 #endif
 
 	p_script->valid=true;
 	return OK;
 }
 
-Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script) {
+Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script,bool p_keep_state) {
 
 	err_line=-1;
 	err_column=-1;
@@ -1772,9 +1836,7 @@ Error GDCompiler::compile(const GDParser *p_parser,GDScript *p_script) {
 
 	source=p_script->get_path();
 
-
-
-	Error err = _parse_class(p_script,NULL,static_cast<const GDParser::ClassNode*>(root));
+	Error err = _parse_class(p_script,NULL,static_cast<const GDParser::ClassNode*>(root),p_keep_state);
 
 	if (err)
 		return err;

+ 2 - 2
modules/gdscript/gd_compiler.h

@@ -144,7 +144,7 @@ class GDCompiler {
 	int _parse_expression(CodeGen& codegen,const GDParser::Node *p_expression, int p_stack_level,bool p_root=false,bool p_initializer=false);
 	Error _parse_block(CodeGen& codegen,const GDParser::BlockNode *p_block,int p_stack_level=0,int p_break_addr=-1,int p_continue_addr=-1);
 	Error _parse_function(GDScript *p_script,const GDParser::ClassNode *p_class,const GDParser::FunctionNode *p_func,bool p_for_ready=false);
-	Error _parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class);
+	Error _parse_class(GDScript *p_script,GDScript *p_owner,const GDParser::ClassNode *p_class,bool p_keep_state);
 	int err_line;
 	int err_column;
 	StringName source;
@@ -152,7 +152,7 @@ class GDCompiler {
 
 public:
 
-	Error compile(const GDParser *p_parser,GDScript *p_script);
+	Error compile(const GDParser *p_parser, GDScript *p_script, bool p_keep_state=false);
 
 	String get_error() const;
 	int get_error_line() const;

+ 121 - 4
modules/gdscript/gd_script.cpp

@@ -101,6 +101,12 @@ GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Obj
 	instance->members.resize(member_indices.size());
 	instance->script=Ref<GDScript>(this);
 	instance->owner=p_owner;
+#ifdef DEBUG_ENABLED
+	//needed for hot reloading
+	for(Map<StringName,MemberInfo>::Element *E=member_indices.front();E;E=E->next()) {
+		instance->member_indices_cache[E->key()]=E->get().index;
+	}
+#endif
 	instance->owner->set_script_instance(instance);
 
 	/* STEP 2, INITIALIZE AND CONSRTUCT */
@@ -500,10 +506,10 @@ void GDScript::_set_subclass_path(Ref<GDScript>& p_sc,const String& p_path) {
 	}
 }
 
-Error GDScript::reload() {
+Error GDScript::reload(bool p_keep_state) {
 
 
-	ERR_FAIL_COND_V(instances.size(),ERR_ALREADY_IN_USE);
+	ERR_FAIL_COND_V(!p_keep_state && instances.size(),ERR_ALREADY_IN_USE);
 
 	String basedir=path;
 
@@ -531,7 +537,7 @@ Error GDScript::reload() {
 	bool can_run = ScriptServer::is_scripting_enabled() || parser.is_tool_script();
 
 	GDCompiler compiler;
-	err = compiler.compile(&parser,this);
+	err = compiler.compile(&parser,this,p_keep_state);
 
 	if (err) {
 
@@ -837,7 +843,7 @@ void GDScript::get_script_signal_list(List<MethodInfo> *r_signals) const {
 }
 
 
-GDScript::GDScript() {
+GDScript::GDScript() : script_list(this) {
 
 
 	_static_ref=this;
@@ -851,12 +857,33 @@ GDScript::GDScript() {
 	source_changed_cache=false;
 #endif
 
+#ifdef DEBUG_ENABLED
+	if (GDScriptLanguage::get_singleton()->lock) {
+		GDScriptLanguage::get_singleton()->lock->lock();
+	}
+	GDScriptLanguage::get_singleton()->script_list.add(&script_list);
+
+	if (GDScriptLanguage::get_singleton()->lock) {
+		GDScriptLanguage::get_singleton()->lock->unlock();
+	}
+#endif
 }
 
 GDScript::~GDScript() {
 	for (Map<StringName,GDFunction*>::Element *E=member_functions.front();E;E=E->next()) {
 		memdelete( E->get() );
 	}
+
+#ifdef DEBUG_ENABLED
+	if (GDScriptLanguage::get_singleton()->lock) {
+		GDScriptLanguage::get_singleton()->lock->lock();
+	}
+	GDScriptLanguage::get_singleton()->script_list.remove(&script_list);
+
+	if (GDScriptLanguage::get_singleton()->lock) {
+		GDScriptLanguage::get_singleton()->lock->unlock();
+	}
+#endif
 }
 
 
@@ -1218,6 +1245,37 @@ ScriptLanguage *GDInstance::get_language() {
 	return GDScriptLanguage::get_singleton();
 }
 
+void GDInstance::reload_members() {
+
+#ifdef DEBUG_ENABLED
+
+	members.resize(script->member_indices.size()); //resize
+
+	Vector<Variant> new_members;
+	new_members.resize(script->member_indices.size());
+
+	//pass the values to the new indices
+	for(Map<StringName,GDScript::MemberInfo>::Element *E=script->member_indices.front();E;E=E->next()) {
+
+		if (member_indices_cache.has(E->key())) {
+			Variant value = members[member_indices_cache[E->key()]];
+			new_members[E->get().index]=value;
+		}
+
+	}
+
+	//apply
+	members=new_members;
+
+	//pass the values to the new indices
+	member_indices_cache.clear();
+	for(Map<StringName,GDScript::MemberInfo>::Element *E=script->member_indices.front();E;E=E->next()) {
+
+		member_indices_cache[E->key()]=E->get().index;
+	}
+
+#endif
+}
 
 GDInstance::GDInstance() {
 	owner=NULL;
@@ -1441,6 +1499,65 @@ int GDScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr,int p_i
 }
 
 
+struct GDScriptDepSort {
+
+	//must support sorting so inheritance works properly (parent must be reloaded first)
+	bool operator()(const Ref<GDScript> &A, const Ref<GDScript>& B) const {
+
+		if (A==B)
+			return false; //shouldn't happen but..
+		const GDScript *I=B->get_base().ptr();
+		while(I) {
+			if (I==A.ptr()) {
+				// A is a base of B
+				return true;
+			}
+
+			I=I->get_base().ptr();
+		}
+
+		return false; //not a base
+	}
+};
+
+void GDScriptLanguage::reload_all_scripts() {
+
+
+
+#ifdef DEBUG_ENABLED
+	print_line("RELOAD ALL SCRIPTS");
+	if (lock) {
+		lock->lock();
+	}
+
+	List<Ref<GDScript> > scripts;
+
+	SelfList<GDScript> *elem=script_list.first();
+	while(elem) {
+		if (elem->self()->get_path().is_resource_file()) {
+			print_line("FOUND: "+elem->self()->get_path());
+			scripts.push_back(Ref<GDScript>(elem->self())); //cast to gdscript to avoid being erased by accident
+		}
+		elem=elem->next();
+	}
+
+	if (lock) {
+		lock->unlock();
+	}
+
+	//as scripts are going to be reloaded, must proceed without locking here
+
+	scripts.sort_custom<GDScriptDepSort>(); //update in inheritance dependency order
+
+	for(List<Ref<GDScript> >::Element *E=scripts.front();E;E=E->next()) {
+
+		print_line("RELOADING: "+E->get()->get_path());
+		E->get()->load_source_code(E->get()->get_path());
+		E->get()->reload(true);
+	}
+#endif
+}
+
 void GDScriptLanguage::frame() {
 
 	//	print_line("calls: "+itos(calls));

+ 14 - 2
modules/gdscript/gd_script.h

@@ -107,6 +107,7 @@ friend class GDScriptLanguage;
 	String source;
 	String path;
 	String name;
+	SelfList<GDScript> script_list;
 
 
 	GDInstance* _create_instance(const Variant** p_args,int p_argcount,Object *p_owner,bool p_isref,Variant::CallError &r_error);
@@ -165,7 +166,7 @@ public:
 	virtual void set_source_code(const String& p_code);
 	virtual void update_exports();
 
-	virtual Error reload();
+	virtual Error reload(bool p_keep_state=false);
 
 	virtual String get_node_type() const;
 	void set_script_path(const String& p_path) { path=p_path; } //because subclasses need a path too...
@@ -186,9 +187,13 @@ class GDInstance : public ScriptInstance {
 friend class GDScript;
 friend class GDFunction;
 friend class GDFunctions;
+friend class GDCompiler;
 
 	Object *owner;
 	Ref<GDScript> script;
+#ifdef DEBUG_ENABLED
+	Map<StringName,int> member_indices_cache; //used only for hot script reloading
+#endif
 	Vector<Variant> members;
 	bool base_ref;
 
@@ -209,7 +214,7 @@ public:
 	virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount);
 	virtual void call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount);
 
-    Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
+	Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
 
 	virtual void notification(int p_notification);
 
@@ -219,6 +224,7 @@ public:
 
 	void set_path(const String& p_path);
 
+	void reload_members();
 
 	GDInstance();
 	~GDInstance();
@@ -256,6 +262,10 @@ class GDScriptLanguage : public ScriptLanguage {
 
 
 	Mutex *lock;
+
+friend class GDScript;
+
+	SelfList<GDScript>::List script_list;
 friend class GDFunction;
 
 	SelfList<GDFunction>::List function_list;
@@ -378,6 +388,8 @@ public:
 	virtual void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems=-1,int p_max_depth=-1);
 	virtual String debug_parse_stack_level_expression(int p_level,const String& p_expression,int p_max_subitems=-1,int p_max_depth=-1);
 
+	virtual void reload_all_scripts();
+
 	virtual void frame();
 
 	virtual void get_public_functions(List<MethodInfo> *p_functions) const;

+ 6 - 0
tools/editor/editor_node.cpp

@@ -2795,6 +2795,10 @@ void EditorNode::_menu_option_confirm(int p_option,bool p_confirmed) {
 			run_native->set_debug_navigation(!ischecked);
 			editor_run.set_debug_navigation(!ischecked);
 		} break;
+		case RUN_RELOAD_SCRIPTS: {
+
+			ScriptEditor::get_singleton()->get_debugger()->reload_scripts();
+		} break;
 		case SETTINGS_UPDATE_ALWAYS: {
 
 			update_menu->get_popup()->set_item_checked(0,true);
@@ -5717,6 +5721,8 @@ EditorNode::EditorNode() {
 	p->add_separator();
 	p->add_check_item(TTR("Visible Collision Shapes"),RUN_DEBUG_COLLISONS);
 	p->add_check_item(TTR("Visible Navigation"),RUN_DEBUG_NAVIGATION);
+	p->add_separator();
+	p->add_item(TTR("Reload Scripts"),RUN_RELOAD_SCRIPTS);
 	p->connect("item_pressed",this,"_menu_option");
 
 	/*

+ 1 - 0
tools/editor/editor_node.h

@@ -174,6 +174,7 @@ private:
 		RUN_DEBUG_COLLISONS,
 		RUN_DEBUG_NAVIGATION,
 		RUN_DEPLOY_REMOTE_DEBUG,
+		RUN_RELOAD_SCRIPTS,
 		SETTINGS_UPDATE_ALWAYS,
 		SETTINGS_UPDATE_CHANGES,
 		SETTINGS_IMPORT,

+ 10 - 0
tools/editor/script_editor_debugger.cpp

@@ -1573,6 +1573,16 @@ void ScriptEditorDebugger::set_breakpoint(const String& p_path,int p_line,bool p
 }
 
 
+void ScriptEditorDebugger::reload_scripts() {
+
+	if (connection.is_valid()) {
+	       Array msg;
+	       msg.push_back("reload_scripts");
+	       ppeer->put_var(msg);
+       }
+}
+
+
 void ScriptEditorDebugger::_error_selected(int p_idx) {
 
 	error_stack->clear();

+ 2 - 0
tools/editor/script_editor_debugger.h

@@ -217,6 +217,8 @@ public:
 
 	void set_tool_button(Button *p_tb) { debugger_button=p_tb; }
 
+	void reload_scripts();
+
 	virtual Size2 get_minimum_size() const;
 	ScriptEditorDebugger(EditorNode *p_editor=NULL);
 	~ScriptEditorDebugger();