-
-
Notifications
You must be signed in to change notification settings - Fork 22k
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
Fix loading scene for every request on script file for workspace completion #101091
base: master
Are you sure you want to change the base?
Fix loading scene for every request on script file for workspace completion #101091
Conversation
186fd24
to
c2ac0a5
Compare
This should remove the delay for subsequent completion requests, the delay would still be there for the initial request. I think we could improve on this by using the textDocument/didOpen and textDocument/didClose signals to manage the cache. This way Godot could start loading the owner, when the script gets opened. Depending on the time between opening and typing something (which can be quite a bit in bigger projects from my experience) this might completely remove the delay or decrease it a bit. Also Godot resource loading system has a builtin cache which would definitely come in handy with the preloading approach. As long as the loaded resource isn't freed it will be reused in the future. We could just cache the references to the loaded scene and re instantiate it since this shouldn't cause much overhead (this would also allow replacing This could work kinda like this:
|
Hey thanks, the SceneCache is only a wrapper for a HashMap with a Scene specific clear() function. Also as these Scenes are loaded resources and their sub resources also, the ResourceLoader already uses the cache for all of them, if there are used in another loaded Scene. For the rest, sounds reasonable. I'll look into it. Also I have really fast completion times even at the beginning now with a production build and large resources (~1GB loaded), as the main slowdown came from the queue of loading->freeing->loading->freeing. |
Yeah right, my main reason for suggesting only relying on the resource loader cache was the idea with the loading headstart, which might get harder to implement when caching the nodes.
That's interesting, I still get the same slow down on the first completion request, with this PR and my MRP (no production build). Maybe we are hitting different bottle necks with loading 🤔 |
Ah k. I used a production build (ucrt). On my dev build it was in deed still very slow, even for the first input. But I did not stop time, so could just be my imagination over being happy that it worked. 🙃 |
c2ac0a5
to
44565a1
Compare
I moved all logic for the Scene loading into its own class SceneCache inside language_server_protocol. Threaded resource Loading is added and used, when "use thread" is activated for the language server. Using threaded resource loading is a bit of a hassle, as there is no option to kill a specific running task. (Or at least I did not found one.) So the server needs to wait for a running task and clean it afterwards, if the client disconnects or closes the document. But for now, I think it works quite well. |
44565a1
to
bb22257
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.
In general I think that when the completion request arrives and the scene isn't loaded yet, we should not continue without owner and instead wait for loading to finish. The owner is not only used for node path completion, but also for argument options (e.g. suggesting animation names when doing $AnimationPlayer.play()
).
Otherwise two subsequent completion requests could lead to different results, I think that's not really great for the user experience.
modules/gdscript/language_server/gdscript_language_protocol.cpp
Outdated
Show resolved
Hide resolved
modules/gdscript/language_server/gdscript_language_protocol.cpp
Outdated
Show resolved
Hide resolved
Hey, thank you very much for reviewing. :) Ah ok, I did not look to deep into the usage of the scene itself for auto completion. I get were you are coming from, but I think it is the better user experience to instantly get an auto completion from the language server after opening a script. |
bb22257
to
8149c3b
Compare
8149c3b
to
2cadadd
Compare
Hey, so from my side it is finished. Worked the last weeks with this in 4.3 and in master and got no problems. Best regards :) |
Just a quick update, this still needs a maintainer approval, but as a side note: Your commit seems not to be linked to your GitHub account. See: Why are my commits linked to the wrong user? for more info. |
…letion For workspace completion the Scene for the script is loaded for every completion request and freed afterwards. Resulting in performance issues for scenes including large resources. Loaded Scenes are now cached for each script and loaded when opening the script. Not loading them again and only freeing them when the connection is terminated, the language server stopped or the script is closed. Add use of a thread for resource loading If use thread is enabled in the language server, threaded load from ResourceLoader will be utilized. Only one load task will be running at a time. Remove unused header
2cadadd
to
8693381
Compare
Thanks for the heads up. I added the e-mail adress. Should be working now. |
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 for the long delay, had some exams to write.
I tested the PR locally and it seems to work fine.
I have to come back to one thing I brought up before though:
In general I think that when the completion request arrives and the scene isn't loaded yet, we should not continue without owner and instead wait for loading to finish.
I was going to say, that this is not a deal breaker for me, but I read a bit more into the spec due to some other PR and noticed that implementation of file management (didOpen
/didClose
) is not required. I don't know any editor that does not implement those, but since it is part of the spec we should have a contingency plan for it. The easiest one, would be to block the current thread and finish loading inside of get
(this would only happen on the first request, and not at all if didOpen
is implemented and finished before).
#include "core/error/error_list.h" | ||
#include "core/io/resource_loader.h" |
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.
#include "core/error/error_list.h" | |
#include "core/io/resource_loader.h" |
#include "core/templates/hash_map.h" | ||
#include "core/variant/array.h" |
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.
#include "core/templates/hash_map.h" | |
#include "core/variant/array.h" |
friend class GDScriptLanguageProtocol; | ||
|
||
HashMap<String, Node *> cache; | ||
Ref<GDScriptWorkspace> workspace; |
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.
The whole LSP is managed through a singleton. No need to keep a reference here, the workspace can be accessed through GDScriptLanguageProtocol::get_singleton()->get_workspace()
instead.
@@ -345,4 +351,209 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() { | |||
set_scope("completionItem", text_document.ptr()); | |||
set_scope("workspace", workspace.ptr()); | |||
workspace->root = ProjectSettings::get_singleton()->get_resource_path(); | |||
scene_cache.workspace = workspace; | |||
} | |||
|
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 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.
Not sure what did go wrong here, and why github is also marking code. I just want to add a separator between the two classes.
scene_cache.workspace = workspace; | ||
} | ||
|
||
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners) { |
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.
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners) { | |
void SceneCache::_get_owners(EditorFileSystemDirectory *p_dir, const String &p_path, List<String> &r_owners) { |
There already is a problem with cryptic names in parts of the codebase and efsd doesn't seem like any commonly known abbreviation. Let's use a name that doesn't require looking up the type to understand.
if (E.value) { | ||
memdelete(E.value); | ||
} |
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.
if (E.value) { | |
memdelete(E.value); | |
} | |
memdelete_notnull(E.value); |
void set(const String &p_path); | ||
void set_for_uri(const String &p_uri); |
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.
Please rename those to something like request
or enqueue
, to make clear that this does not instantly set the value.
@@ -82,6 +83,7 @@ void GDScriptTextDocument::didChange(const Variant &p_param) { | |||
doc.text = evt.text; | |||
} | |||
sync_script_content(doc.uri, doc.text); | |||
GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get_for_uri(doc.uri); |
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.
The return value is discarded. Why do we need this here?
* Does only one threaded request to the ResourceLoader at a time. | ||
* Because loading the same subresources in parallel can bring up errors in the editor. | ||
* */ | ||
void SceneCache::_add_owner_scene_request(String p_path = "") { |
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 method is doing too much (otherwise you wouldn't need a private default value).
We should split this up in one method for enqueuing new paths. And one for updating the queue. (The former can obviously call to the later)
} | ||
} | ||
|
||
void SceneCache::_check_thread_for_cache_update() { |
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.
Splitting this function up would make sense. The part beginning from ResourceLoader::load_threaded_get
should be its own function. This would allow to easily remove the custom spin locks from erase
and clear
.
scene_cache.workspace = workspace; | ||
} | ||
|
||
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners) { |
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.
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners) { | |
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, LocalVector<String> &r_owners) { |
List
should not be used in new Code anymore as per the c++ usage guidelines
Instead of marking all usages: |
For workspace completion the Scene for the script is loaded for every completion request and freed afterwards. Resulting in performance issues for scenes including large resources.
Loaded Scenes are now cached for each script. Not loading them again and only freeing them when the connection is terminated or the language server stopped.
This PR fixes #100934
I have no experience in C++ development - feedback is of course more than welcome.
Unit Tests are green and auto formatting should have worked too.
Bugsquad edit (keywords for easier searching): LSP, language server