Skip to content

Commit 56f3e26

Browse files
committed
Merge pull request godotengine#114559 from HolonProduction/lsp/fix-performance-scene-loading-workspace-completion
LSP: Fix loading scene for every request on script file for workspace completion
2 parents de6a487 + a4d029f commit 56f3e26

8 files changed

Lines changed: 276 additions & 58 deletions

File tree

modules/gdscript/language_server/gdscript_language_protocol.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ Error GDScriptLanguageProtocol::on_client_connected() {
148148

149149
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
150150
clients.erase(p_client_id);
151+
if (clients.is_empty()) {
152+
scene_cache.clear();
153+
}
151154
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
152155
}
153156

@@ -269,6 +272,8 @@ void GDScriptLanguageProtocol::poll(int p_limit_usec) {
269272
on_client_connected();
270273
}
271274

275+
scene_cache.poll();
276+
272277
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
273278
while (E != clients.end()) {
274279
Ref<LSPeer> peer = E->value;
@@ -315,6 +320,7 @@ void GDScriptLanguageProtocol::stop() {
315320
peer->connection->disconnect_from_host();
316321
}
317322

323+
scene_cache.clear();
318324
server->stop();
319325
}
320326

@@ -447,6 +453,8 @@ void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
447453

448454
client->managed_files[path] = document;
449455
client->parse_script(path);
456+
457+
scene_cache.request_load(path);
450458
}
451459

452460
void GDScriptLanguageProtocol::lsp_did_change(const Dictionary &p_params) {
@@ -492,6 +500,8 @@ void GDScriptLanguageProtocol::lsp_did_close(const Dictionary &p_params) {
492500

493501
/// A close notification requires a previous open notification to be sent.
494502
ERR_FAIL_COND_MSG(!was_opened, "LSP: Client is closing file without opening it.");
503+
504+
scene_cache.unload(path);
495505
}
496506

497507
void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {

modules/gdscript/language_server/gdscript_language_protocol.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "gdscript_text_document.h"
3434
#include "gdscript_workspace.h"
35+
#include "scene_cache.h"
3536

3637
#include "core/io/stream_peer_tcp.h"
3738
#include "core/io/tcp_server.h"
@@ -92,6 +93,7 @@ class GDScriptLanguageProtocol : public JSONRPC {
9293
static GDScriptLanguageProtocol *singleton;
9394

9495
HashMap<int, Ref<LSPeer>> clients;
96+
SceneCache scene_cache;
9597
Ref<TCPServer> server;
9698
int latest_client_id = LSP_NO_CLIENT;
9799
int next_client_id = 0;
@@ -119,6 +121,8 @@ class GDScriptLanguageProtocol : public JSONRPC {
119121
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
120122
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
121123
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
124+
_FORCE_INLINE_ SceneCache *get_scene_cache() { return &scene_cache; }
125+
122126
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
123127

124128
void poll(int p_limit_usec);

modules/gdscript/language_server/gdscript_workspace.cpp

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
#include "editor/editor_node.h"
4242
#include "editor/file_system/editor_file_system.h"
4343
#include "editor/settings/editor_settings.h"
44-
#include "scene/resources/packed_scene.h"
4544

4645
void GDScriptWorkspace::_bind_methods() {
4746
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
@@ -592,59 +591,14 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
592591
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
593592
}
594593

595-
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
596-
if (!efsd) {
597-
return;
598-
}
599-
600-
for (int i = 0; i < efsd->get_subdir_count(); i++) {
601-
_get_owners(efsd->get_subdir(i), p_path, owners);
602-
}
603-
604-
for (int i = 0; i < efsd->get_file_count(); i++) {
605-
Vector<String> deps = efsd->get_file_deps(i);
606-
bool found = false;
607-
for (int j = 0; j < deps.size(); j++) {
608-
if (deps[j] == p_path) {
609-
found = true;
610-
break;
611-
}
612-
}
613-
if (!found) {
614-
continue;
615-
}
616-
617-
owners.push_back(efsd->get_file_path(i));
618-
}
619-
}
620-
621-
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
622-
Node *owner_scene_node = nullptr;
623-
List<String> owners;
624-
625-
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
626-
627-
for (const String &owner : owners) {
628-
NodePath owner_path = owner;
629-
Ref<Resource> owner_res = ResourceLoader::load(String(owner_path));
630-
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
631-
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
632-
owner_scene_node = owner_packed_scene->instantiate();
633-
break;
634-
}
635-
}
636-
637-
return owner_scene_node;
638-
}
639-
640594
void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {
641595
String path = get_file_path(p_params.textDocument.uri);
642596
String call_hint;
643597
bool forced = false;
644598

645599
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
646600
if (parser) {
647-
Node *owner_scene_node = _get_owner_scene_node(path);
601+
Node *owner_scene_node = GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get(path);
648602

649603
Array stack;
650604
Node *current = nullptr;
@@ -670,9 +624,6 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
670624

671625
String code = parser->get_text_for_completion(p_params.position);
672626
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
673-
if (owner_scene_node) {
674-
memdelete(owner_scene_node);
675-
}
676627
}
677628
}
678629

modules/gdscript/language_server/gdscript_workspace.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,11 @@
3535
#include "godot_lsp.h"
3636

3737
#include "core/variant/variant.h"
38-
#include "editor/file_system/editor_file_system.h"
3938

4039
class GDScriptWorkspace : public RefCounted {
4140
GDCLASS(GDScriptWorkspace, RefCounted);
4241

4342
private:
44-
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
45-
Node *_get_owner_scene_node(String p_path);
46-
4743
#ifndef DISABLE_DEPRECATED
4844
void didDeleteFiles() {}
4945
Error parse_script(const String &p_path, const String &p_content) {

modules/gdscript/language_server/godot_lsp.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@
3434
#include "core/object/class_db.h"
3535
#include "core/templates/list.h"
3636

37+
// Enable additional LSP related logging.
38+
//#define DEBUG_LSP
39+
40+
#ifdef DEBUG_LSP
41+
#define LOG_LSP(...) print_line("[ LSP -", __FILE__, ":", __LINE__, "-", __func__, "] -", ##__VA_ARGS__)
42+
#else
43+
#define LOG_LSP(...)
44+
#endif
45+
3746
namespace LSP {
3847

3948
typedef String DocumentUri;
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/**************************************************************************/
2+
/* scene_cache.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "scene_cache.h"
32+
33+
#include "godot_lsp.h"
34+
35+
#include "core/io/resource_loader.h"
36+
#include "editor/file_system/editor_file_system.h"
37+
#include "scene/resources/packed_scene.h"
38+
39+
void SceneCache::_get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths) {
40+
if (!p_dir) {
41+
return;
42+
}
43+
44+
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
45+
_get_owner_paths(p_dir->get_subdir(i), p_script_path, r_owner_paths);
46+
}
47+
48+
for (int i = 0; i < p_dir->get_file_count(); i++) {
49+
if (p_dir->get_file_deps(i).has(p_script_path)) {
50+
r_owner_paths.push_back(p_dir->get_file_path(i));
51+
}
52+
}
53+
}
54+
55+
void SceneCache::_finalize_scene_load() {
56+
ERR_FAIL_COND(current_loaded_owner.is_empty() || script_path_queue.is_empty());
57+
58+
Ref<PackedScene> scene_res = ResourceLoader::load_threaded_get(current_loaded_owner);
59+
60+
if (scene_res.is_valid()) {
61+
cache[script_path_queue[0]] = scene_res->instantiate();
62+
} else {
63+
cache[script_path_queue[0]] = nullptr;
64+
}
65+
66+
LOG_LSP("Scene cached for script:", script_path_queue[0]);
67+
LOG_LSP("pending_script_queue length:", script_path_queue.size() - 1);
68+
69+
script_path_queue.remove_at(0);
70+
current_loaded_owner = String();
71+
}
72+
73+
void SceneCache::poll() {
74+
if (current_loaded_owner.is_empty()) {
75+
// No load ongoing, start the next one.
76+
77+
if (EditorFileSystem::get_singleton()->is_scanning() || script_path_queue.is_empty()) {
78+
return;
79+
}
80+
81+
LocalVector<String> owners;
82+
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), script_path_queue[0], owners);
83+
for (const String &owner : owners) {
84+
if (ResourceLoader::load_threaded_request(owner) == Error::OK) {
85+
current_loaded_owner = owner;
86+
LOG_LSP("Scene load started for:", current_loaded_owner);
87+
break;
88+
}
89+
}
90+
91+
if (current_loaded_owner.is_empty()) {
92+
cache[script_path_queue[0]] = nullptr;
93+
LOG_LSP("No scene found for script:", script_path_queue[0]);
94+
script_path_queue.remove_at(0);
95+
LOG_LSP("pending_script_queue length:", script_path_queue.size());
96+
}
97+
} else {
98+
ERR_FAIL_COND(script_path_queue.is_empty());
99+
100+
// There is an ongoing load. Check the status.
101+
102+
ResourceLoader::ThreadLoadStatus status = ResourceLoader::load_threaded_get_status(current_loaded_owner);
103+
104+
if (status == ResourceLoader::THREAD_LOAD_IN_PROGRESS) {
105+
return;
106+
}
107+
108+
if (status == ResourceLoader::THREAD_LOAD_LOADED) {
109+
_finalize_scene_load();
110+
} else {
111+
LOG_LSP("Scene load failure for:", current_loaded_owner);
112+
cache[script_path_queue[0]] = nullptr;
113+
114+
script_path_queue.remove_at(0);
115+
current_loaded_owner = String();
116+
}
117+
}
118+
}
119+
120+
Node *SceneCache::get(const String &p_script_path) {
121+
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
122+
_finalize_scene_load();
123+
} else {
124+
script_path_queue.erase(p_script_path);
125+
}
126+
127+
if (Node **entry = cache.getptr(p_script_path)) {
128+
return *entry;
129+
}
130+
131+
// Fallback to blocking load. This could happen if the open request was only recently sent.
132+
// TODO: This could also happen when multiple clients are connected.
133+
134+
LocalVector<String> owners;
135+
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), p_script_path, owners);
136+
for (const String &owner : owners) {
137+
Ref<PackedScene> scene = ResourceLoader::load(owner);
138+
if (scene.is_valid()) {
139+
Node *instance = scene->instantiate();
140+
cache[p_script_path] = instance;
141+
return instance;
142+
}
143+
}
144+
145+
cache[p_script_path] = nullptr;
146+
return nullptr;
147+
}
148+
149+
void SceneCache::request_load(const String &p_script_path) {
150+
if (!cache.has(p_script_path) && !script_path_queue.has(p_script_path)) {
151+
script_path_queue.push_back(p_script_path);
152+
LOG_LSP("Scene load requested for:", p_script_path);
153+
LOG_LSP("pending_script_queue length:", script_path_queue.size());
154+
}
155+
}
156+
157+
void SceneCache::unload(const String &p_script_path) {
158+
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
159+
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
160+
161+
script_path_queue.remove_at(0);
162+
current_loaded_owner = String();
163+
} else {
164+
script_path_queue.erase(p_script_path);
165+
}
166+
167+
if (!cache.has(p_script_path)) {
168+
return;
169+
}
170+
memdelete_notnull(cache[p_script_path]);
171+
cache.erase(p_script_path);
172+
LOG_LSP("Cache cleared for path:", p_script_path);
173+
}
174+
175+
void SceneCache::clear() {
176+
if (!current_loaded_owner.is_empty()) {
177+
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
178+
current_loaded_owner = String();
179+
}
180+
script_path_queue.clear();
181+
for (const KeyValue<String, Node *> &E : cache) {
182+
memdelete_notnull(E.value);
183+
}
184+
cache.clear();
185+
LOG_LSP("Cache cleared.");
186+
}

0 commit comments

Comments
 (0)