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)

Most upvoted comments

@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 includes onready variable initializations), but also that setget 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:

tool
extends Node

export(int) var selection = 0 setget set_selection
onready var children = get_children()

func set_selection(v):
  # "children" will sometimes be null!
  print(children)
  selection = v

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 the is_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 and reloaded (Just an example, I don’t like these names). In Unity you have OnEnable and OnDisable 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, but setget setters for exports are run. Using this, I was able to add a functional _reload() callback like so:

extends Node2D
tool

export(bool) var _reload = false setget _reload

func _reload(x):
    # perform reload logic here

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.