godot: Tool script doesn't call _ready or _enter_tree on reload
Godot version: 3.0 stable 3.0.1 Build 689dfcd
OS/device including version: Windows 7 Home
Issue description: I have written a tool script, that draws connections between nodes. For that I have an onready variable, that loads the nodes. In the _draw function I get the position of the nodes and draw lines between them. The lines are only drawn once after I reload the editor. When I change the script, nothing is drawn. The problem is, that the onready variable is undefined after reloading the script. _ready and _enter_tree aren’t called either. I have tried notifications, too. But no notifications for ready or enter_tree are received.
Steps to reproduce:
- create a new scene
- add two Node2D
- add a tool script to one node
tool
extends Node2D
var node
func _ready():
node = get_node("../Node2D2")
func _process(delta):
update()
func _draw():
draw_circle(node.position, 30, Color(1, 0, 0))
- when you reload the editor, the circle is drawn
- change the radius of the circle and save the script
- the circle is not redrawn
Minimal reproduction project: Test2.zip
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 24
- Comments: 49 (23 by maintainers)
@volzhs That example points out the exact issue here, if you wanted to change it to draw a polygon you would change the code, Ctrl+S and node would be lost causing errors until you reload the scene.
I see no issues with the proposal of calling _ready() on script reload, if it causes side-effects it should be my job to deal with them by not blindly adding new nodes without checking to make sure they don’t already exist. If I save a script, it should behave exactly as it would if I just attached it to a node, not in a way that is unique to the action of saving.
This is a painful issue! I spent a day trying to understand what was going on. The problem is not only that
_ready
would not be called again (and this includesonready
variable initializations), but also thatsetget
setters for exported properties would be called again.(Moreover, the editor will always call the setters before
_ready
if the property was given a non-default custom value. This, at least, makes sense, because you would likely want_ready
to take the “real” values into account. But of course if_ready
isn’t called at all, you are stuck in an in-between state.)Example:
There were a few suggestions here on how to solve this problem. I’ll add my two cents: it would be nice if there was a
_reload
built-in callback function exactly for handling these situations. The logic for reloading is simply different from the logic for adding to the tree (readying). It wouldn’t be trivial, but at least it would be clear where you should put code to handle this situation.Furthermore, I don’t think such a callback should be reserved just for tool scripts! After all, any runtime node in the game could be reloaded dynamically, too.
What if we created a specific method for the use-case of “re-initializing a tool script after it has been saved”? You could define a
_tool_reload()
method and then just have that method and the_ready()
method both execute a separate initialization method. That way the same logic gets executed in-game when the script is included, in-editor when you set it up as a dock/panel/etc. as the editor opens, and when you save the script within the editor. Could even implement custom logic for each specific case (since the_ready()
could use theis_editor_hint()
to differentiate between the first two cases).Even if there are other potential solutions that are more severe, like reloading the scene, this particular issue directly addresses the problem of not having an initialization method in the script that triggers when you save a tool script. Rather than forcing the other methods to get triggered during that situation, we might as well come up with a separate method to handle that use case altogether, as it gives us the highest level of customization, as far as I tell.
This is the behavior of an editor plugin. I don’t like too. Every time a script is changed you have to reload entire editor to make plugin works. Make plugins is this way is a bit annoying, but there is for now other system. It will be fine to have option to reload editor without close and open (or automatic reload on editorplugin script change, but this is more difficult)
Scripts should have two callbacks:
about_to_reload
andreloaded
(Just an example, I don’t like these names). In Unity you haveOnEnable
andOnDisable
which are called on reload (they are also called in other situations that would not apply to Godot).The problem I see is that
_ready
and_enter_tree
would loose their semantic. Who knows what people put in these functions, but it’s to deal with when these events actually happen. Having them happen exceptionally on script reloading breaks that semantic, which might come unexpected, hence the possible side-effects, and why reloading the scene would not have these side-effects (for everyone, not just you). Also, what about the_init()
constructor function? Same story here, would it be called again?Another example of the problem I had with my plugin: https://github.com/Zylann/godot_terrain_plugin/blob/master/addons/zylann.terrain/terrain.gd#L54 The sub-nodes and VisualScript RIDs created by the terrain would be leaked and created a second time, over the existing ones which my script forgets about because of getting reset by intrusive reloading. Having
_ready()
called again wouldn’t solve anything, at least in that case. Hence again why, normally exiting the tree first and re-entering by reloading the scene works (or, in my case, not resetting data and only reloading functions). And reloading the scene is far from hot-reloading the script.I had this problem too with tool scripts. The problem is, script reloading RESETS variables. If it didnt do that, the script could be able to still function normally, provided that your changes are not too big. Recalling ready() would mean reloading the scene anyways (which you need to do manually at the moment, no need to restart the entire editor). So part of your game/tool state would still reset in some dirty way, which is certainly not intented.
So I believe the problem is not about those functions not being re-called, it’s about member variables getting reset, while they should not. They should be remembered, and restored after the reload.
Note: think also about resources, or just simple reference or object scripts. They are plagued with the same problem, except these do not have
_ready
or_enter_tree
. It’s not just node scripts.I think your code does not have the same problem as this issue states. Instead, your code has a design issue, which will also happen when you close and reopen the scene: it tries to access a child node inside a setter of an exported variable. However setters are called when objects of a scene are created, even before its children are. And that happens before it is added to the tree._ready
is called after it is added to the tree. Usually to implement this you should check if the node is inside the tree, and assign variables or child nodes in_ready
in addition to the setters.Looking closer, I see your property only has the
PROPERTY_USAGE_EDITOR
. So technically that means it’s not a serialized property… hence it’s actually odd that these properties get called at all during saving of the scene 🤔 maybe that’s still another issue though? Another possibility is that your script might have had changes, or Godot decided to save it regardless, which would trigger the present issue of resetting every member variable.As @tliron noted,
_ready()
and_enter_tree()
aren’t called when reloading a tool, butsetget
setters forexports
are run. Using this, I was able to add a functional_reload()
callback like so:This definitely feels hacky, and will add a
Reload
checkbox to the scene editor for your tool, but otherwise seems to work alright.It shouldn’t be the job of Godot to worry about the users code. If a programmer is writing bad code, it’s his/her own fault. And in a tool script, I want the reload to reset everything to default and call _enter_tree and _ready. There is no second time here.
The problem is how your script works. Calling ready and enter tree again supposes your scene is getting readded, which can have side effects such as connecting signals or creating nodes twice, because the node would get enter tree events while not really being added to the tree. Which means reloading the scene would work better in that case…
Really, I believe proper script reloading simply needs to restore ALL member variables as they were.