-
-
Notifications
You must be signed in to change notification settings - Fork 21.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve Scene Tree editor performance #99700
Improve Scene Tree editor performance #99700
Conversation
Before: (this scene has about 15,000 nodes) Screencast.From.2024-11-26.02-11-22.mp4After: Screencast.From.2024-11-26.02-10-35.mp4 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stealing @AThousandShips 's job 🤣
6aefc0b
to
9a25be0
Compare
431094f
to
05519d4
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirm that birdies are in the nest (dots at the end of the comments)😃
05519d4
to
d4d55d0
Compare
Changing valid types does not update the nodes (they stay disabled/enabled). Can be tested with this simple EditorScript: extends EditorScript
func _run() -> void:
EditorInterface.popup_node_selector(func(n): print(n), ["Node2D"]) Run it, then remove "Node2D" and run again. Nodes that didn't match originally will stay disabled. |
c5fca34
to
e076f60
Compare
This comment was marked as outdated.
This comment was marked as outdated.
e076f60
to
8851ae1
Compare
47465d3
to
987fdd4
Compare
I'm not a fan of even more editor-specific code in non-editor code. Why not add the signal onto the object using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what it's worth, I've hammered the TPS Demo project's scenes trees with random changes, undid them, redid them, etc and haven't been able to break it.
After this pr we can remove several editor only signals as well, it if sadly not possible to add the signals at runtime as they get emitted from parts of node that just don't have signals. Like when a node gets added to a persistent group. However the ultimate net effect is going to be less overall editor specific code in node. |
I tried this on my Jungle Demo scene and (compared to 4.4dev6) the time it takes for re-parenting a node is reduced from ~49 seconds to ~1 second. Amazing work! I, however, feels like the scene takes a bit longer to load in editor, (~10% longer). |
If you would be willing to share your project with me in some fashion I can have a look at that also. But this would likely be addressed in a follow-up pr. You can find me on the rocket.chat with username |
I think the two commits could be squashed together, there isn't much reason to implement the functionality in one commit and expose it in the next, we usually implement and expose at the same time. |
if (Object::cast_to<Material>(resource)) { | ||
// For performance reasons, assume that Materials don't have NodePaths in them. | ||
// TODO This check could be removed when String performance has improved. | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this use the PROPERTY_HINT_NO_NODEPATH
hint you added instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the problem here is that getting the property list itself is super slow. By the time I get the hint it is already too late.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/godotengine/godot/blob/master/scene/resources/material.cpp#L2404
This is the function that I need to avoid calling. There's a way to make it a bit faster too, but even then it is still pretty slow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I realized this might break ViewportTextures in materials. They won't update paths anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They still work, I forgot to put that as a test in my list of things I tested. But that was specifically tested.
I put it in two separate commits in case any backport to 4.3 wouldn't want to expose it. I can squash them though if that is not actually desired. |
I'd say it's fine to squash. If we do a 4.3 backport, I think exposing a minor new functionality wouldn't be a big deal compared to the very significant refactoring of both editor code and core logic, which might be riskier than just a new function. From a quick test it wouldn't be a simple |
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 godotengine#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.
987fdd4
to
6f7525c
Compare
Alright! Squashed. |
Can we has merge this so it can be tested in the next dev build prz? 👉👈 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
YOLO
Thanks! |
void SceneTreeEditor::set_marked(const HashSet<Node *> &p_marked, bool p_selectable, bool p_children_selectable) { | ||
_update_if_clean(); | ||
|
||
_update_marking_list(marked); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am getting a crash within this function occasionally when opening the signal connection dialog due to garbage data in marked
. Is calling _update_marking_list()
twice intended? Commenting out the first call seems to fix the issue, but I can't tell if that breaks anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, calling it twice is intentional. Do you have a back trace of the crash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should never be "garbage data" in marked
that is a pre-existing bug then, but I can't figure out how garbage data could get into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@hilloftheking can you try adding marked.clear()
to https://github.com/godotengine/godot/blob/master/editor/gui/scene_tree_editor.cpp#L909 and see if this solves the issue?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm away for a few days. I should have opened an issue for this but I couldn't make a decent reproduction. I think it has to do with the connection dialog keeping data from a closed scene if anyone else can make a reproduction project. If not, I will make one when I get back.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does keep data from a closed scene until it reopens, this is by design because updating the invisible SceneTreeEditors is very costly. It makes everything take 6x as long! :)
I will see if I can come up with a fix for what I hypothesize is the problem.
} | ||
|
||
if (p_node->has_signal(SceneStringName(visibility_changed))) { | ||
if (p_node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &SceneTreeEditor::_node_visibility_changed))) { | ||
p_node->disconnect(SceneStringName(visibility_changed), callable_mp(this, &SceneTreeEditor::_node_visibility_changed)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This signal is no longer disconnected. Is that okay? Is it part of the performance savings? Will it cause any issues in long running editor sessions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is by design, the cost of connecting and disconnecting signals here is pretty high, and leaving them connected doesn't really do anything. If it turns out to be a problem it can be changed back, but I have not observed any issues with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These signals do not fire often, and a signal that's not firing is just a bit of memory, and not a lot.
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, @daniel080400 for their tireless testing. And @AeioMuch for their testing and providing a fix for the hover issue.