Reimplement scene change

This commit is contained in:
Pedro J. Estébanez 2023-07-04 13:28:43 +02:00
parent 46424488ed
commit 4354cd8d41
3 changed files with 42 additions and 26 deletions

View File

@ -42,7 +42,7 @@
<description> <description>
Changes the running scene to the one at the given [param path], after loading it into a [PackedScene] and creating a new instance. Changes the running scene to the one at the given [param path], after loading it into a [PackedScene] and creating a new instance.
Returns [constant OK] on success, [constant ERR_CANT_OPEN] if the [param path] cannot be loaded into a [PackedScene], or [constant ERR_CANT_CREATE] if that scene cannot be instantiated. Returns [constant OK] on success, [constant ERR_CANT_OPEN] if the [param path] cannot be loaded into a [PackedScene], or [constant ERR_CANT_CREATE] if that scene cannot be instantiated.
[b]Note:[/b] The scene change is deferred, which means that the new scene node is added to the tree at the end of the frame. 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]. As such, you won't be able to access the loaded scene immediately after the [method change_scene_to_file] call. [b]Note:[/b] The new scene node is added to the tree at the end of the frame. 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]. As such, you won't be able to access the loaded scene immediately after the [method change_scene_to_file] call.
</description> </description>
</method> </method>
<method name="change_scene_to_packed"> <method name="change_scene_to_packed">
@ -51,7 +51,7 @@
<description> <description>
Changes the running scene to a new instance of the given [PackedScene] (which must be valid). Changes the running scene to a new instance of the given [PackedScene] (which must be valid).
Returns [constant OK] on success, [constant ERR_CANT_CREATE] if the scene cannot be instantiated, or [constant ERR_INVALID_PARAMETER] if the scene is invalid. Returns [constant OK] on success, [constant ERR_CANT_CREATE] if the scene cannot be instantiated, or [constant ERR_INVALID_PARAMETER] if the scene is invalid.
[b]Note:[/b] The scene change is deferred, which means that the new scene node is added to the tree at the end of the frame. You won't be able to access it immediately after the [method change_scene_to_packed] call. [b]Note:[/b] The new scene node is added to the tree at the end of the frame. You won't be able to access it immediately after the [method change_scene_to_packed] call.
</description> </description>
</method> </method>
<method name="create_timer"> <method name="create_timer">

View File

@ -550,6 +550,10 @@ bool SceneTree::process(double p_time) {
#endif // _3D_DISABLED #endif // _3D_DISABLED
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
if (unlikely(pending_new_scene)) {
_flush_scene_change();
}
return _quit; return _quit;
} }
@ -1378,28 +1382,17 @@ Node *SceneTree::get_current_scene() const {
return current_scene; return current_scene;
} }
void SceneTree::_change_scene(Node *p_to) { void SceneTree::_flush_scene_change() {
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "Changing scene can only be done from the main thread."); if (prev_scene) {
if (current_scene) { memdelete(prev_scene);
memdelete(current_scene); prev_scene = nullptr;
current_scene = nullptr;
} }
current_scene = pending_new_scene;
// If we're quitting, abort. root->add_child(pending_new_scene);
if (unlikely(_quit)) { pending_new_scene = nullptr;
if (p_to) { // Prevent memory leak.
memdelete(p_to);
}
return;
}
if (p_to) {
current_scene = p_to;
root->add_child(p_to);
// Update display for cursor instantly. // Update display for cursor instantly.
root->update_mouse_cursor_state(); root->update_mouse_cursor_state();
} }
}
Error SceneTree::change_scene_to_file(const String &p_path) { Error SceneTree::change_scene_to_file(const String &p_path) {
ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Changing scene can only be done from the main thread."); ERR_FAIL_COND_V_MSG(!Thread::is_main_thread(), ERR_INVALID_PARAMETER, "Changing scene can only be done from the main thread.");
@ -1417,7 +1410,22 @@ Error SceneTree::change_scene_to_packed(const Ref<PackedScene> &p_scene) {
Node *new_scene = p_scene->instantiate(); Node *new_scene = p_scene->instantiate();
ERR_FAIL_NULL_V(new_scene, ERR_CANT_CREATE); ERR_FAIL_NULL_V(new_scene, ERR_CANT_CREATE);
call_deferred(SNAME("_change_scene"), new_scene); // If called again while a change is pending.
if (pending_new_scene) {
queue_delete(pending_new_scene);
pending_new_scene = nullptr;
}
prev_scene = current_scene;
if (current_scene) {
// Let as many side effects as possible happen or be queued now,
// so they are run before the scene is actually deleted.
root->remove_child(current_scene);
}
DEV_ASSERT(!current_scene);
pending_new_scene = new_scene;
return OK; return OK;
} }
@ -1595,8 +1603,6 @@ void SceneTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("reload_current_scene"), &SceneTree::reload_current_scene); ClassDB::bind_method(D_METHOD("reload_current_scene"), &SceneTree::reload_current_scene);
ClassDB::bind_method(D_METHOD("unload_current_scene"), &SceneTree::unload_current_scene); ClassDB::bind_method(D_METHOD("unload_current_scene"), &SceneTree::unload_current_scene);
ClassDB::bind_method(D_METHOD("_change_scene"), &SceneTree::_change_scene);
ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer", "root_path"), &SceneTree::set_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("set_multiplayer", "multiplayer", "root_path"), &SceneTree::set_multiplayer, DEFVAL(NodePath()));
ClassDB::bind_method(D_METHOD("get_multiplayer", "for_path"), &SceneTree::get_multiplayer, DEFVAL(NodePath())); ClassDB::bind_method(D_METHOD("get_multiplayer", "for_path"), &SceneTree::get_multiplayer, DEFVAL(NodePath()));
ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled); ClassDB::bind_method(D_METHOD("set_multiplayer_poll_enabled", "enabled"), &SceneTree::set_multiplayer_poll_enabled);
@ -1835,6 +1841,14 @@ SceneTree::SceneTree() {
} }
SceneTree::~SceneTree() { SceneTree::~SceneTree() {
if (prev_scene) {
memdelete(prev_scene);
prev_scene = nullptr;
}
if (pending_new_scene) {
memdelete(pending_new_scene);
pending_new_scene = nullptr;
}
if (root) { if (root) {
root->_set_tree(nullptr); root->_set_tree(nullptr);
root->_propagate_after_exit_tree(); root->_propagate_after_exit_tree();

View File

@ -179,6 +179,8 @@ private:
TypedArray<Node> _get_nodes_in_group(const StringName &p_group); TypedArray<Node> _get_nodes_in_group(const StringName &p_group);
Node *current_scene = nullptr; Node *current_scene = nullptr;
Node *prev_scene = nullptr;
Node *pending_new_scene = nullptr;
Color debug_collisions_color; Color debug_collisions_color;
Color debug_collision_contact_color; Color debug_collision_contact_color;
@ -189,7 +191,7 @@ private:
Ref<Material> collision_material; Ref<Material> collision_material;
int collision_debug_contacts; int collision_debug_contacts;
void _change_scene(Node *p_to); void _flush_scene_change();
List<Ref<SceneTreeTimer>> timers; List<Ref<SceneTreeTimer>> timers;
List<Ref<Tween>> tweens; List<Ref<Tween>> tweens;