Repository: godotengine/godot (language: cpp)

Issue:
`change_scene_to_*` needs to await for two `process_frame`s
### Tested versions

Godot 4.2+

### System information

Arch Linux

### Issue description

`change_scene_to_*()` methods always change scene in a deferred way.

The documentation says scene change happens at the end of the frame. But I have to await `SceneTree.process_frame` twice instead of once since #78988.

https://github.com/godotengine/godot/blob/2d0ee20ff30461b6b10f6fdfba87511a0ebc6642/doc/classes/SceneTree.xml#L55-L56

```gdscript
var tree := get_tree()

tree.change_scene_to_file(path)

await tree.process_frame
# expected & old behavior: new scene available here
# actual: still not available

await tree.process_frame
# actual: new scene available here
```

### Steps to reproduce

See above, or use the MRP.

### Minimal reproduction project (MRP)

[test-change-scene.zip](https://github.com/godotengine/godot/files/13700325/test-change-scene.zip)



Discussion / hints:
Okay, I thought it's because I put the code snippet inside `_unhandled_input()` and `process_frame` signals the start of `process()`.

But doesn't `_unhandled_input()` happen at the end of last frame by default?
`_unhandled_input()` happens without delay right after `_input()` and the other `_*_input()` calls.
The execution of physics picking happens asynchronously in the next frame.

@Sauermann Where does it happen inside a frame relative to `_process()`?

* Before `_process()`: Then that explains it. But what signal should I await for (besides awaiting for `process_frame` twice) in order to access the new scene?
* After `_process()`: Then it's a bug.
That I don't know. I haven't yet looked into the order of things happening within a single frame.
```GDScript
func change_scene(path: String) -> void:
	print("------ changing to %s ------" % path)
	var tree := get_tree()
	
	print("[Before] %s" % tree.current_scene, " - frame: ", Engine.get_process_frames())
	tree.change_scene_to_file(path)
	
	await tree.process_frame
	print("[after tree.process_frame] %s" % tree.current_scene, " - frame: ", Engine.get_process_frames())
	
	await tree.process_frame
	print("[after tree.process_frame] %s" % tree.current_scene, " - frame: ", Engine.get_process_frames())
```

Output:
```
[Before] OldScene:<Node2D#27095205063> - frame: 65
[after tree.process_frame] <Object#null> - frame: 65
[after tree.process_frame] NewScene:<Node2D#29645341898> - frame: 66
```

The first await isn't really waiting, when the call comes from `_unhandled_input` or `_input`.
However, if you instead use `_process`:
```
func _process(delta: float) -> void:
	if Input.is_action_just_pressed("ui_accept"):
		Autoload.change_scene("res://old_scene.tscn")
```
The output will now be:
```
[Before] OldScene:<Node2D#27095205063> - frame: 101
[after tree.process_frame 1] NewScene:<Node2D#31088182474> - frame: 102
[after tree.process_frame 2] NewScene:<Node2D#31088182474> - frame: 103
```
If nothing else, this works as a work-around for the MRP.
This seems like an important caveat with `await tree.process_frame` that we should document.
> Where does it happen inside a frame relative to `_process()`?
> 
> * Before `_process()`: Then that explains it. But what signal should I await for (besides awaiting for `process_frame` twice) in order to access the new scene?
> 
> * After `_process()`: Then it's a bug.

It's kinda irrelevant whether it happens before/after `_process()`. If `change_scene_to_*` is called anywhere outside [`process()`](https://github.com/godotengine/godot/blob/2d0ee20ff30461b6b10f6fdfba87511a0ebc6642/scene/main/scene_tree.cpp#L486-L558) (within the red interval in the diagram below) then it's guaranteed that after awaiting `process_frame` signal the scene change has not happened yet because flushing scene change is done within `process()` after emitting `process_frame` signal.

The same can be told about the black interval in the diagram below.

For awaiting single `process_frame` to be enough the `change_scene_to_*` would need to be called somewhere within the pink interval (so e.g. in `_process()`, like in https://github.com/godotengine/godot/issues/86286#issuecomment-1860662955).

![dCDWa8XAe4](https://github.com/godotengine/godot/assets/9283098/d79a82ca-1c68-45f6-a8e6-2c15bc5d60e7)


> [!NOTE]
> I kinda ignored threading when analyzing this so it might be not that simple. :upside_down_face:

---

Anyway here's I think a somehow reliable way to detect/await the scene change:
```gdscript
extends Node

signal _scene_changed

@onready var _tree: SceneTree = get_tree()

func change_scene(path: String) -> Error:
	var error: Error = _tree.change_scene_to_file(path)
	if error == OK:
		if not _tree.node_added.is_connected(_on_node_added):
			_tree.node_added.connect(_on_node_added)
		await _scene_changed
		# Scene changed (and ready).
	return error

func _on_node_added(node: Node) -> void:
	if node == _tree.current_scene:
		_tree.node_added.disconnect(_on_node_added)
		# Here `_tree.current_scene` is already changed.
		# But note we are in the middle of executing `tree.root.add_child(current_scene)`.
		# So likely deferring it a little further is wanted, e.g. until its `ready` signal.
		await _tree.current_scene.ready
		_scene_changed.emit()
```
Given it's autoloaded as `SceneChanger` it could be used like `var error = await SceneChanger.change_scene(path)`.

It works because `current_scene` is assigned before `add_child()`:
https://github.com/godotengine/godot/blob/bf8dd73e9d88d6ab146506699f43230b12c6a2b0/scene/main/scene_tree.cpp#L1396-L1406
Thanks for the workaround. I think `await tree.tree_changed` also works if the user can guarentee autoloads don't touch the scene tree during the scene change.
I wish there was a signal `scene_changed` built into SceneTree. There's no signal that I know which happens at the end of the frame, after the physics and process calls. That's when the scene change happens.

Is there some proposal for this to happen?

For reference, here's a [processing diagram](/godotengine/godot-docs/issues/9305) that I did for Godot 4. Here we can see how the input is handled at the very start of the frame and the scene is changed after the SceneTree.process_frame signal. Exactly as @kleonc explained.

Another possible workaround could be a timer in idle processing mode. It would trigger just after the scene change. So `await get_tree().create_timer(0).timeout`.

Fix patch:
diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml
index 77baef9d0898..9a31492d2018 100644
--- a/doc/classes/SceneTree.xml
+++ b/doc/classes/SceneTree.xml
@@ -58,6 +58,7 @@
 				1. The current scene node is immediately removed from the tree. From that point, [method Node.get_tree] called on the current (outgoing) scene will return [code]null[/code]. [member current_scene] will be [code]null[/code], too, because the new scene is not available yet.
 				2. At the end of the frame, the formerly current scene, already removed from the tree, will be deleted (freed from memory) and then the new scene will be instantiated and added to the tree. [method Node.get_tree] and [member current_scene] will be back to working as usual.
 				This ensures that both scenes aren't running at the same time, while still freeing the previous scene in a safe way similar to [method Node.queue_free].
+				If you want to reliably access the new scene, await the [signal scene_changed] signal.
 			</description>
 		</method>
 		<method name="create_timer">
@@ -312,6 +313,17 @@
 				Emitted immediately before [method Node._process] is called on every node in this tree.
 			</description>
 		</signal>
+		<signal name="scene_changed">
+			<description>
+				Emitted after the new scene is added to scene tree and initialized. Can be used to reliably access [member current_scene] when changing scenes.
+				[codeblock]
+				# This code should be inside an autoload.
+				get_tree().change_scene_to_file(other_scene_path)
+				await get_tree().scene_changed
+				print(get_tree().current_scene) # Prints the new scene.
+				[/codeblock]
+			</description>
+		</signal>
 		<signal name="tree_changed">
 			<description>
 				Emitted any time the tree's hierarchy changes (nodes being moved, renamed, etc.).
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 4043eb55ac96..557fced85ed1 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -1517,6 +1517,8 @@ void SceneTree::_flush_scene_change() {
 	pending_new_scene = nullptr;
 	// Update display for cursor instantly.
 	root->update_mouse_cursor_state();
+
+	emit_signal(SNAME("scene_changed"));
 }
 
 Error SceneTree::change_scene_to_file(const String &p_path) {
@@ -1784,6 +1786,7 @@ void SceneTree::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled");
 
 	ADD_SIGNAL(MethodInfo("tree_changed"));
+	ADD_SIGNAL(MethodInfo("scene_changed"));
 	ADD_SIGNAL(MethodInfo("tree_process_mode_changed")); //editor only signal, but due to API hash it can't be removed in run-time
 	ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
 	ADD_SIGNAL(MethodInfo("node_removed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));


Test patch: