-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Avoid unnecessary inspector updates when loading or switching scenes #80517
Avoid unnecessary inspector updates when loading or switching scenes #80517
Conversation
I just tested it with hyperfine to see if there are measurable startup improvements when restoring scenes, and sure as day, there are: Command that was used
Test 1. 2 open scenes
Test 2. 2 open scenes
Test 3. 9 open scenes
Test 4. 9 open scenes
Tests 1&2 and 3&4 used the same setup for each pair and are used to double-check the results. All runs were performed on unoptimized dev builds (so they are generally slower than what you'd expected from official builds). From these you can see that load times are now consistent no matter how many scenes you have opened by default. |
The changes look good. I found a couple more unnecessary updates (in the main inspector): There's a bunch of methods here calling tree update: godot/editor/inspector_dock.cpp Lines 762 to 773 in 4714e95
This seems to assign default profile, triggering inspector update: Line 677 in 4714e95
Maybe we could add a flag to the inspector, like |
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 alright in the current form (except that one style comment). It also fixes some issues related to weird TileMap editor behavior on launch.
Further improvements can be done later.
This should result in some noticeable performance improvements, aside from fixing bugs due to conflicts in logic. This also simplifies some related code identified while debugging.
0018f8a
to
2445414
Compare
Yeah, I agree. If there are many setters that can trigger it, it would be better to have a global suppression flag rather than pass one to each setter. If implemented, the new argument added to |
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.
Great job, @YuriSizov. Your PR is very thorough, detailed and well explained.
Thanks! |
Fixes #80126. Maybe some other errors too — related to plugins, inspector updates, and editor startup. For example, #73650.
So the issue seems like it has something to do with plugins, but it's not. The core of the problem lies in the fact that we like to excessively trigger
update_tree
on the inspector, which can result in multiple calls to edit the same object from different contexts in the same frame. Or result in requests for editing from scenes which are going to be replaced during the loading procedure. Overall I identified the following cases related to the issue:load_scene
. Each such call results in the loaded scene to be passed to the rest of the editor as the currently edited object... But in the very next second we remove the scene to load the next one. And set the next one as the currently edited object. And so on. When we're done with this, we finish the whole thing off with using the stored scene index to set it as the currently edited object AGAIN.This sucks. Every attempt to edit anything builds the tree in the inspector, including subtrees if some properties are resources and are unfolded. This triggers a whole bunch of things. Only for the scene to be discarded immediately, so all is done for naught. At the same time if the last opened scene is the one that should be the currently edited scene, it is marked for editing twice. At best, this is very slow, unnecessarily so. But at worse, this leads to various bugs such as #80126.
You see, we call some methods with deferred calls. And when we open a scene with the root being Control node that happens to have a theme (quite a normal thing to have), we register one such deferred call. If this scene happens to be the last one and we mark it for editing again, then we make the second deferred call. So the next time the message queue is flushed we make two calls to add an editor for the same resource, resulting in the logged error.
To reiterate what happens, let's consider an example of two restored scenes where on has a control with a theme as its root. In our case this is the last scene on the list and it's also the scene that was last edited before shutting down the editor. The following happens:
If Scene 2 is not the last edited scene, then there won't be an error, but curiously the theme editor for its embedded theme would still be open upon loading. Despite a different scene, Scene 1, being currently active.
I resolved this by guarding the
push_item
call inload_scene
when we are in the process of restoring scenes. This should work fine. Maybe there is some weird case if we have scenes to restore, but don't have the last edited scene stored, but I don't think this is possible without user intervention and at this point the worst that should happen is no scene being edited. A couple of clicks should solve that.update_tree
call to be triggered multiple times within the same setup process. This once again leads to conflicting logic and poor performance. I think one of the cases where I was able to reproduce the "plugin already exists" error unrelated to loading can be attributed to this.The case was this: run any scene, switch to the remote view and select any node, then switch back to the edited scene with a control node that has a theme and select it. Boom, you have the same error logged.
#35526 is particularly suspicious, as I'm not entirely sure why rebuilding the tree would somehow restore the folded status that is supposedly broken when you rename a node. I was unable to reproduce this with the hack removed. The PR is old, so probably some 3.x weirdness, or maybe the solution wasn't appropriate even back then, can't really say for sure at this point.
#19568 does more than the title says. One thing that it adds is a call to
set_use_folding
to update that setting according to the editor configuration. Setting aside if this is the best way to do it, this call comes with an extra side effect: it also rebuilds the tree. The tree that we, for sure, just updated during the previous steps ofEditorNode::_edit_current
. Solution here is simple, add a flag to disable the tree update (so other use cases are unaffected) and make sure to move the call to before we build the tree.While investigating I noticed that
EditorNode::edit_item
can be made a bit more flat and clear, so I updated it. The logic should be identical to before, except for one extra call tohide_unused_editors(p_editing_owner);
that I think we were missing if the edited object is disabled by a feature profile.I also renamed some related methods of
EditorData
, as their existing names were making it a bit confusing to read the code. These are internal, so it shouldn't lead to any issues. The names may not be ideal, but they should be clearer than before at least.Finally, I noticed a bunch of calls like
EditorNode::get_singleton()->get_editor_data()
in related code, so I did a pass that simplifies it toEditorNode::get_editor_data()
.