Improve Scene Tree editor performance

We now cache the Node*<>TreeItem* mapping in the SceneTreeEditor. This
allows us to make targeted updates to the Tree used to display the scene
tree in the editor.

Previously on almost all changes to the scene tree the editor would
rebuild the entire widget, causing a large number of deallocations an
allocations. We now carefully manipulate the Tree widget in-situ saving
a large number of these allocations.

In order to know what Nodes need to be updated we add a
editor_state_changed signal to Node, this is a TOOLS_ENABLED,
editor-only signal fired when changes to Node happen that are relevant
to editor state.

We also now make sure that when nodes are moved/renamed we don't check
expensive properties that cannot contain NodePaths. This saves a lot of
time when SceneTreeDock renames a node in a scene with a lot of
MeshInstances. This makes renaming nodes go from ~27 seconds to ~2
seconds on large scenes.

SceneTreeEditor instances will now also not do all of the potentially
expensive update work if they are invisible. This behavior is turned off
by default so it won't affect existing users. This change allows the
editor to only update SceneTreeEditors that actually in view. In
practice this means that for most changes instead of updating 6
SceneTreeEditors we only update 1 instantly, and the others only when
they become visible.

There is definitely more that could be done, but this is already a
massive improvement. In complex scenes we see an improvement of 10x,
things that used to take ~30 seconds now only take 2.

This fixes #83460

I want to thank KoBeWi, TokisanGames, a-johnston, aniel080400 for
their tireless testing. And AeioMuch for their testing and providing a
fix for the hover issue.
This commit is contained in:
HP van Braam
2024-11-26 00:04:25 +01:00
parent 44dfa7e710
commit 6f7525c396
15 changed files with 818 additions and 181 deletions

View File

@@ -678,6 +678,8 @@ void Node::set_process_mode(ProcessMode p_mode) {
if (Engine::get_singleton()->is_editor_hint()) {
get_tree()->emit_signal(SNAME("tree_process_mode_changed"));
}
_emit_editor_state_changed();
#endif
}
@@ -2163,6 +2165,7 @@ void Node::set_unique_name_in_owner(bool p_enabled) {
}
update_configuration_warnings();
_emit_editor_state_changed();
}
bool Node::is_unique_name_in_owner() const {
@@ -2200,6 +2203,8 @@ void Node::set_owner(Node *p_owner) {
if (data.unique_name_in_owner) {
_acquire_unique_name_in_owner();
}
_emit_editor_state_changed();
}
Node *Node::get_owner() const {
@@ -2383,6 +2388,9 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) {
gd.persistent = p_persistent;
data.grouped[p_identifier] = gd;
if (p_persistent) {
_emit_editor_state_changed();
}
}
void Node::remove_from_group(const StringName &p_identifier) {
@@ -2393,11 +2401,21 @@ void Node::remove_from_group(const StringName &p_identifier) {
return;
}
#ifdef TOOLS_ENABLED
bool persistent = E->value.persistent;
#endif
if (data.tree) {
data.tree->remove_from_group(E->key, this);
}
data.grouped.remove(E);
#ifdef TOOLS_ENABLED
if (persistent) {
_emit_editor_state_changed();
}
#endif
}
TypedArray<StringName> Node::_get_groups() const {
@@ -2560,6 +2578,7 @@ Ref<Tween> Node::create_tween() {
void Node::set_scene_file_path(const String &p_scene_file_path) {
ERR_THREAD_GUARD
data.scene_file_path = p_scene_file_path;
_emit_editor_state_changed();
}
String Node::get_scene_file_path() const {
@@ -2592,6 +2611,8 @@ void Node::set_editable_instance(Node *p_node, bool p_editable) {
} else {
p_node->data.editable_instance = true;
}
p_node->_emit_editor_state_changed();
}
bool Node::is_editable_instance(const Node *p_node) const {
@@ -2702,6 +2723,7 @@ Ref<SceneState> Node::get_scene_instance_state() const {
void Node::set_scene_inherited_state(const Ref<SceneState> &p_state) {
ERR_THREAD_GUARD
data.inherited_state = p_state;
_emit_editor_state_changed();
}
Ref<SceneState> Node::get_scene_inherited_state() const {
@@ -2950,6 +2972,14 @@ void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Re
}
}
}
void Node::_emit_editor_state_changed() {
// This is required for the SceneTreeEditor to properly keep track of when an update is needed.
// This signal might be expensive and not needed for anything outside of the editor.
if (Engine::get_singleton()->is_editor_hint()) {
emit_signal(SNAME("editor_state_changed"));
}
}
#endif
// Duplicate node's properties.
@@ -3843,6 +3873,7 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("child_order_changed"));
ADD_SIGNAL(MethodInfo("replacing_by", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_SIGNAL(MethodInfo("editor_description_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_SIGNAL(MethodInfo("editor_state_changed"));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner");
@@ -3966,11 +3997,13 @@ bool Node::has_meta(const StringName &p_name) const {
void Node::set_meta(const StringName &p_name, const Variant &p_value) {
ERR_THREAD_GUARD;
Object::set_meta(p_name, p_value);
_emit_editor_state_changed();
}
void Node::remove_meta(const StringName &p_name) {
ERR_THREAD_GUARD;
Object::remove_meta(p_name);
_emit_editor_state_changed();
}
Variant Node::get_meta(const StringName &p_name, const Variant &p_default) const {
@@ -4020,12 +4053,33 @@ void Node::get_signals_connected_to_this(List<Connection> *p_connections) const
Error Node::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
ERR_THREAD_GUARD_V(ERR_INVALID_PARAMETER);
return Object::connect(p_signal, p_callable, p_flags);
Error retval = Object::connect(p_signal, p_callable, p_flags);
#ifdef TOOLS_ENABLED
if (p_flags & CONNECT_PERSIST) {
_emit_editor_state_changed();
}
#endif
return retval;
}
void Node::disconnect(const StringName &p_signal, const Callable &p_callable) {
ERR_THREAD_GUARD;
#ifdef TOOLS_ENABLED
// Already under thread guard, don't check again.
int old_connection_count = Object::get_persistent_signal_connection_count();
#endif
Object::disconnect(p_signal, p_callable);
#ifdef TOOLS_ENABLED
int new_connection_count = Object::get_persistent_signal_connection_count();
if (old_connection_count != new_connection_count) {
_emit_editor_state_changed();
}
#endif
}
bool Node::is_connected(const StringName &p_signal, const Callable &p_callable) const {