Ability to reload scripts on running game

This commit is contained in:
Juan Linietsky 2016-06-01 20:22:02 -03:00
parent 45752eaae4
commit 9e745b920f
12 changed files with 247 additions and 18 deletions

View file

@ -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;
}

View file

@ -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();
};

View file

@ -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));
}

View file

@ -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();

View file

@ -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;
Error err = _parse_class(subclass.ptr(),p_script,p_class->subclasses[i]);
if (old_subclasses.has(name)) {
subclass=old_subclasses[name];
} else {
subclass.instance();
}
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;

View file

@ -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;

View file

@ -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));

View file

@ -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;
@ -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;

View file

@ -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");
/*

View file

@ -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,

View file

@ -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();

View file

@ -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();