Ability to reload scripts on running game
This commit is contained in:
parent
45752eaae4
commit
9e745b920f
12 changed files with 247 additions and 18 deletions
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
||||
/*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue