godot: Exported variables break setget methods that are "ready" dependent

Godot version: 3.1.6

OS/device including version: Solus Gnome 4.0

Issue description:

Alright, this sounds kinda complex, but the issue is simple, so I’ll give context first.

I have this script:

extends Node

export (NodePath) var node_path = ".."
onready var node = get_node(node_path)

export (bool) var enalbed = true setget set_enabled

func set_enabled(enable):
    enabled = enable
    node.set_process(enabled)

The thing is, if I change the Enabled value in the inspector, the set_enabled method crashes when running the scene, because seems like the method is called before the _ready callback, or at least before the onready variable being set. So it reports that node is null, therefore it is unabled to call set_process.

I think that this could be fixed by changing the order in which setget methods are called, making them wait for idle process at least?

Steps to reproduce:

  1. Create a Node
  2. Add a child Node
  3. In the child Node add a script
  4. Copy paste the script above mentioned
  5. In the inspector set Enabled to false
  6. Test the scene

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 17 (15 by maintainers)

Most upvoted comments

What I always do is

if not is_inside_tree(): yield(self, 'ready')

This will delay the setters instead of returning and not setting anything at all. Still I don’t like typing it out every time.

For this I figured I should update such properties deferred most of the time:

extends Node

export (NodePath) var node_path = ".."
onready var node = get_node(node_path)

export (bool) var enabled = true setget set_enabled

func set_enabled(enable):
    enabled = enable
    call_deferred('_update_properties') # updated on idle (next) frame

func _update_properties():
    node.set_process(enabled)

The above mechanism may cause _update_properties called multiple times in a single frame. Here’s more a sophisticated and optimized mechanism if you need to change properties that update often (picked this up from engine internals):


extends Node

export (NodePath) var node_path = ".."
onready var node = get_node(node_path)

export (bool) var enabled = true setget set_enabled

var _update_queued = false

func set_enabled(enable):
    enabled = enable
   _queue_update()

func _update_properties():
    node.set_process(enabled)
    # might as well do more stuff
    _update_queued = false

func _queue_update():
    if not is_inside_tree():
        return
    if _update_queued:
        return

    _update_queued = true

    call_deferred("_update_properties")

I usually do this in my scripts like so:

export var x = 5 setget set_x

func _ready():
  set_x(x)

func set_x(new_x):
  x = new_x
  if is_inside_tree():
    # do something, like:
    $sprite.frame = x

Note that #6491 will help remove a few lines of that boilerplate, even more if it is made to work with onready nicely.

won’t work either because Timer works using process and process is not called when the node is not in the tree ¯(ツ)

Yeah, but it will work AFTER it’s added to tree. The idea is basically to wait until everything is set up. set_process is useless outside tree anyways.

EDIT: Or what @santouits wrote plus this:

var ready

func _ready():
    node.set_process(enabled)
    ready = true

func set_enabled(enable):
    enabled = enable
    if ready:
       node.set_process(enabled)

There is the Node.is_inside_tree() method for that, this fixed the problem by the way!

func set_enabled(enable):
        .set_enabled(enable)
	if not is_inside_tree():
		return
	
	if enable:
		emit_signal("started")
		platform_actor.velocity = Vector2.ZERO
	else:
		emit_signal("finished")