godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.05k stars 65 forks source link

Add a signal for when scripts reload #9620

Open ydeltastar opened 2 weeks ago

ydeltastar commented 2 weeks ago

Describe the project you are working on

Prototyping a project. Hot reloading is great for fast iterations.

Describe the problem or limitation you are having in your project

Godot can hot reload edited scripts in both the editor and game running but there is no way to know if it happened. I often want to call a re-init function when a script reloads but there are no signals or callbacks for this event in Script or the various Editor interfaces and plugin extensions. We can't get notified from outside too like catching GDScript reloads from C# and GDExtension.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Script will emit a reloaded signal so we can be notified. It could be used like this:

var script = get_script() as Script
script.reloaded.connect(print.bind("%s reloaded" % script))

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Update: I implemented it in https://github.com/godotengine/godot/pull/91319

Emit it at the end of Script::reload Script.reload() can be used both at the editor and runtime so this signal shouldn't be an editor-only feature.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I have to re-implement the file-watching system, dynamic source loading, and synchronization the editor already does.

Is there a reason why this should be core and not an add-on in the asset library?

There are no signals or callbacks for script reloading.

RadiantUwU commented 2 weeks ago

Due to reload being an abstract virtual method internally, it looks like it will have to modify GDScript and CSharp as well for this to work.

This also requires modifying GDExtension to have it emit this.

RedMser commented 2 weeks ago

It appears to me like the script's _static_init gets called as part of the Script::reload. Have you tried to see if this is the case and can be used?

It's a static func, but you can find a way to get a list of instances of your script e.g. via a static var tracking the objects (add to list on _init), or through an Autoload Singleton that tracks all interesting reload events as global signals that the script listens to.

ydeltastar commented 2 weeks ago

Due to reload being an abstract virtual method internally, it looks like it will have to modify GDScript and CSharp as well for this to work.

I don't think the C# module supports individual script reloading yet, it reloads the whole assembly so the signal will be triggered for all scripts. It also doesn't restore the previous state like in GDScript so signal connections are lost after reloading. GDExtension doesn't work like managed scripts, we can't compile a C++ class at runtime using Godot's API.

It appears to me like the script's _static_init gets called as part of the Script::reload. Have you tried to see if this is the case and can be used?

I didn't notice I could use _static_init since it doesn't appear in the class reference. This could be a solution but it is called before the reloading process restores the state so static vars aren't assigned to previous values yet.

This will always print an empty array.

static var instances = []

static func _static_init() -> void:
    print(instances)

func _init() -> void:
    instances.append(self)

call_deferred has the same result. It will need to depend on other scripts and extra variables as you suggested. We can't get notified from outside too without modifying the script or catching GDScript reloads from C# or GDExtension. A post-reload signal would be more convenient.

dalexeev commented 2 weeks ago

Have you tried the Resource.changed signal? A script is a resource too.

ydeltastar commented 2 weeks ago

Have you tried the Resource.changed signal? A script is a resource too.

That's for changes in properties, it doesn't emit on script reload. Script reloading is a different process: reloading from the source, checking for syntax errors, compiling at runtime, and restoring state.