Making a node jump into current scene and having trouble. (remove_child from viewport issue? timing issue? See edit 2.)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By skonar2

So. I’m trying to understand my options for moving data around scenes, so I’m experimenting. My current experiment is trying to work out if it’s possible to make a node that can, when the scene switches, jump into the new scene. Without reference to any other code I make - just that node and the script in the node.

The way I’m trying to do this is to have a node - traveller - which is instanced with a button push. I can then name it, so I can be sure I have the same traveller in the next scene.

Traveller listens to the scenetree. When it changes, traveller runs return to see if the current scene (from get_currentscene) is different to the scene it thinks it’s attached to. If it is, it tries to attach to the new current scene.

If its parent node exits, it attempts to _leave and attach to the root node, so it doesn’t get deleted. (As I understand it, a node not attached to anything, which also isn’t a singleton/autoload, deletes itself?)

Traveller is a Node2D, with a child TextureButton, child Button named NameButton, child Label, and child LineEdit.

Here’s my code, and I will explain a bit more below:

    extends Node2D

## Traveller. This is the Traveller, a test for moving nodes and information between scenetrees. Traveller has a unique name attached to a label node, with data fed in from a LineEdit node.

var myroot
var mycurrentscene

# Called when the node enters the scene tree for the first time.
func _ready():
	_initvars()
	myroot.connect("tree_changed",self,"_return")
	get_parent().connect("tree_exiting",self,"_leave")
	connect("tree_exited",self,"_perish")

func _initvars():
	myroot = get_tree()
	mycurrentscene = myroot.get_current_scene()

func _return(): # Check if the current scene has changed, and if it has, jump to the new current scene.
	if mycurrentscene != myroot.get_current_scene() && myroot.get_current_scene() != null:
		print("Traveller returning... from " + str(mycurrentscene) + " to " + 
		str(myroot.get_current_scene()) ) # An announcement for debug
		get_parent().remove_child(self)
		myroot.get_current_scene().add_child(self)
		_initvars()

func _leave(): #Detach from parent node, and try to attach to the scene tree root?
	print("Traveller Departing " + str(mycurrentscene)) # Announcement for debug
	get_parent().remove_child(self)
	myroot.get_root().add_child(self)
	_initvars()

func _perish(): # Announcement for debug, announce if this node has left the tree. At which point it gets deleted? I think? 
	print("Traveller has perished.")

func _on_NameButton_pressed():
	$Label.text = "My name is " + $LineEdit.text 
	pass # Replace with function body.

func _on_TextureButton_pressed():
	print(str(get_parent())) # Debug to make sure I know what Traveller is attached to.
	pass # Replace with function body.

Okay. When I run this, the idea is that the _return func should trigger and swap over to the current scene.

Here’s what I get out of the debug announcements from a run attempt:

Traveller Departing [Control:1195]
Traveller has perished.
Traveller returning... from [Deleted Object] to [Node2D:1649]
Traveller returning... from [Deleted Object] to [Node2D:1649]
Traveller returning... from [Deleted Object] to [Node2D:1649]
(last line repeats until stack overflow)

The editor crashes and throws a ton of this error at me:

E 0:00:03.581   _notification: Condition "!get_viewport()" is true.
  <C++ Source>  scene/main/node.cpp:99 @ _notification()
  <Stack Trace> Traveller.gd:23 @ _return()
                Traveller.gd:23 @ _return()

Anyone know what’s going wrong? Any advice for what I should be doing? Am I trying to do something that flat out isn’t supported by the engine, and I can’t do this with an individual script on an individual node? It 100% requires me to use other nodes and scripts?

A copy of the project can be found at: Dropbox - TravellerTrouble.zip - Simplify your life

There’s a button to create a traveller, and one to switch between two scenes. (It’s also really just a mess of code I’m using to try and understand some stuff.)

EDIT:

Playing around with this, the error seems to be very much tied to the line get_parent().remove_child(self), and the viewport. Is detaching children from the viewport something that needs to be done differently?

EDIT 2:

I got it sort of working by adding a yield to my code here:

func _return(): # Check if the current scene has changed, and if it has, jump to the new current scene.
	if mycurrentscene != myroot.get_current_scene() && myroot.get_root() == get_parent() && myroot.get_current_scene() != null:
		print("Traveller returning... from " + str(mycurrentscene) + " to " + 
		str(myroot.get_current_scene()) + " Parent is " + str(get_parent())) # For debug
		yield(get_tree().create_timer(0.2), "timeout") # Will a pause help? It helped. WHY?
		get_parent().remove_child(self)
		myroot.get_current_scene().add_child(self)
		_initvars()
		request_ready()

But I don’t understand WHY adding the yield on a short timer made it work. Can anyone explain this to me?

:bust_in_silhouette: Reply From: Lopy

When changing scenes, there are a couple of tics, where there are none. Actually, I would recommend changing your timer to a triple yield idle_frame, in case loading your scene from disk takes more than 0.2 seconds.

You could use autoloads to help you. They are direct children of root, and persist over a scene change.

Nodes can survive outside of the tree, they are then called “orphan nodes” and can be tracked using Perfomance.get_monitor(Perfomance.OBJECT_ORPHAN_NODE_COUNT).

To help you decipher when things happen, you can use print_stack().

Just wanted to come back and say thanks - your answer opened up some great stuff for me to explore when it comes to this experiment and helped me learn more about nodes. Really appreciate you taking the time to answer.

skonar2 | 2021-01-03 22:21