Skip to content

Commit 8693381

Browse files
committed
Fix loading scene for every request on script file for workspace completion
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
1 parent 394508d commit 8693381

5 files changed

+252
-59
lines changed

modules/gdscript/language_server/gdscript_language_protocol.cpp

+212-1
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131
#include "gdscript_language_protocol.h"
3232

3333
#include "core/config/project_settings.h"
34+
#include "core/error/error_list.h"
35+
#include "core/io/resource_loader.h"
3436
#include "editor/doc_tools.h"
3537
#include "editor/editor_help.h"
3638
#include "editor/editor_log.h"
3739
#include "editor/editor_node.h"
3840
#include "editor/editor_settings.h"
41+
#include "scene/resources/packed_scene.h"
3942

4043
GDScriptLanguageProtocol *GDScriptLanguageProtocol::singleton = nullptr;
4144

@@ -136,6 +139,7 @@ Error GDScriptLanguageProtocol::on_client_connected() {
136139

137140
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
138141
clients.erase(p_client_id);
142+
scene_cache.clear();
139143
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
140144
}
141145

@@ -236,6 +240,8 @@ void GDScriptLanguageProtocol::poll(int p_limit_usec) {
236240
on_client_connected();
237241
}
238242

243+
scene_cache._check_thread_for_cache_update();
244+
239245
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
240246
while (E != clients.end()) {
241247
Ref<LSPeer> peer = E->value;
@@ -281,7 +287,7 @@ void GDScriptLanguageProtocol::stop() {
281287
Ref<LSPeer> peer = clients.get(E.key);
282288
peer->connection->disconnect_from_host();
283289
}
284-
290+
scene_cache.clear();
285291
server->stop();
286292
}
287293

@@ -345,4 +351,209 @@ GDScriptLanguageProtocol::GDScriptLanguageProtocol() {
345351
set_scope("completionItem", text_document.ptr());
346352
set_scope("workspace", workspace.ptr());
347353
workspace->root = ProjectSettings::get_singleton()->get_resource_path();
354+
scene_cache.workspace = workspace;
355+
}
356+
357+
void SceneCache::_get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners) {
358+
if (!p_efsd) {
359+
return;
360+
}
361+
362+
for (int i = 0; i < p_efsd->get_subdir_count(); i++) {
363+
_get_owners(p_efsd->get_subdir(i), p_path, r_owners);
364+
}
365+
366+
for (int i = 0; i < p_efsd->get_file_count(); i++) {
367+
Vector<String> deps = p_efsd->get_file_deps(i);
368+
bool found = false;
369+
for (int j = 0; j < deps.size(); j++) {
370+
if (deps[j] == p_path) {
371+
found = true;
372+
break;
373+
}
374+
}
375+
if (!found) {
376+
continue;
377+
}
378+
379+
r_owners.push_back(p_efsd->get_file_path(i));
380+
}
381+
}
382+
383+
void SceneCache::_set_owner_scene_node(const String &p_path) {
384+
if (cache.has(p_path)) {
385+
return;
386+
}
387+
388+
Node *owner_scene_node = nullptr;
389+
List<String> owners;
390+
391+
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
392+
owners_path_cache[p_path] = owners;
393+
394+
for (const String &owner : owners) {
395+
NodePath owner_path = owner;
396+
Ref<Resource> owner_res = ResourceLoader::load(owner_path);
397+
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
398+
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
399+
owner_scene_node = owner_packed_scene->instantiate();
400+
break;
401+
}
402+
}
403+
404+
cache[p_path] = owner_scene_node;
405+
}
406+
407+
/**
408+
* Does only one threaded request to the ResourceLoader at a time.
409+
* Because loading the same subresources in parallel can bring up errors in the editor.
410+
* */
411+
void SceneCache::_add_owner_scene_request(String p_path = "") {
412+
if (p_path.is_empty()) {
413+
if (resource_request_queue.size() > 0) {
414+
p_path = resource_request_queue.front();
415+
} else {
416+
return;
417+
}
418+
}
419+
if (cache.has(p_path) && resource_request_queue.size() == 0) {
420+
return;
421+
}
422+
if (!cache.has(p_path) && !resource_request_queue.has(p_path)) {
423+
resource_request_queue.push_back(p_path);
424+
}
425+
if (is_loading) {
426+
return;
427+
}
428+
429+
String path = resource_request_queue.front();
430+
if (!owners_path_cache.has(p_path)) {
431+
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), path, owners_path_cache[p_path]);
432+
}
433+
Error r_error = Error::FAILED;
434+
while (r_error != Error::OK && owners_path_cache[p_path].size() > 0) {
435+
NodePath owner_path = owners_path_cache[p_path].front()->get();
436+
r_error = ResourceLoader::load_threaded_request(owner_path);
437+
if (r_error != Error::OK) {
438+
owners_path_cache[path].pop_front();
439+
}
440+
}
441+
if (owners_path_cache[path].size() > 0) {
442+
is_loading = true;
443+
} else {
444+
cache[path] = nullptr;
445+
owners_path_cache.erase(path);
446+
resource_request_queue.pop_front();
447+
_add_owner_scene_request();
448+
}
449+
}
450+
451+
void SceneCache::_check_thread_for_cache_update() {
452+
if (!is_loading) {
453+
return;
454+
}
455+
456+
String check_path = resource_request_queue.front();
457+
458+
NodePath owner_path = owners_path_cache[check_path].front()->get();
459+
460+
if (ResourceLoader::load_threaded_get_status(owner_path) != ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED) {
461+
return;
462+
}
463+
464+
is_loading = false;
465+
466+
Ref<PackedScene> owner_res = ResourceLoader::load_threaded_get(owner_path);
467+
if (owner_res.is_valid()) {
468+
cache[check_path] = owner_res->instantiate();
469+
owners_path_cache.erase(check_path);
470+
resource_request_queue.pop_front();
471+
_add_owner_scene_request();
472+
return;
473+
}
474+
owners_path_cache[check_path].pop_front();
475+
if (owners_path_cache[check_path].size() == 0) {
476+
cache[check_path] = nullptr;
477+
owners_path_cache.erase(check_path);
478+
resource_request_queue.pop_front();
479+
}
480+
_add_owner_scene_request();
481+
}
482+
483+
bool SceneCache::has(const String &p_path) {
484+
return cache.has(p_path);
485+
}
486+
487+
Node *SceneCache::get(const String &p_path) {
488+
bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
489+
if (remote_use_thread) {
490+
_check_thread_for_cache_update();
491+
_add_owner_scene_request(p_path);
492+
}
493+
return cache.has(p_path) ? cache[p_path] : nullptr;
494+
}
495+
496+
Node *SceneCache::get_for_uri(const String &p_uri) {
497+
String path = workspace->get_file_path(p_uri);
498+
return get(path);
499+
}
500+
501+
void SceneCache::set(const String &p_path) {
502+
bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
503+
if (remote_use_thread) {
504+
_check_thread_for_cache_update();
505+
_add_owner_scene_request(p_path);
506+
} else {
507+
_set_owner_scene_node(p_path);
508+
}
509+
}
510+
511+
void SceneCache::set_for_uri(const String &p_uri) {
512+
String path = workspace->get_file_path(p_uri);
513+
set(path);
514+
}
515+
516+
void SceneCache::erase(const String &p_path) {
517+
bool remote_use_thread = (bool)_EDITOR_GET("network/language_server/use_thread");
518+
if (remote_use_thread && resource_request_queue.has(p_path)) {
519+
if (is_loading && resource_request_queue.front() == p_path) {
520+
while (is_loading) {
521+
_check_thread_for_cache_update();
522+
OS::get_singleton()->delay_usec(50000);
523+
}
524+
} else {
525+
resource_request_queue.erase(p_path);
526+
}
527+
}
528+
if (!cache.has(p_path)) {
529+
return;
530+
}
531+
if (cache[p_path]) {
532+
memdelete(cache[p_path]);
533+
}
534+
cache.erase(p_path);
535+
owners_path_cache.erase(p_path);
536+
}
537+
538+
void SceneCache::erase_for_uri(const String &p_uri) {
539+
String path = workspace->get_file_path(p_uri);
540+
erase(path);
541+
}
542+
543+
void SceneCache::clear() {
544+
if (is_loading) {
545+
while (is_loading) {
546+
_check_thread_for_cache_update();
547+
OS::get_singleton()->delay_usec(100);
548+
}
549+
}
550+
resource_request_queue.clear();
551+
for (const KeyValue<String, Node *> &E : cache) {
552+
if (E.value) {
553+
memdelete(E.value);
554+
}
555+
}
556+
cache.clear();
557+
owners_path_cache.clear();
558+
is_loading = false;
348559
}

modules/gdscript/language_server/gdscript_language_protocol.h

+35
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131
#ifndef GDSCRIPT_LANGUAGE_PROTOCOL_H
3232
#define GDSCRIPT_LANGUAGE_PROTOCOL_H
3333

34+
#include "core/templates/hash_map.h"
35+
#include "core/variant/array.h"
3436
#include "gdscript_text_document.h"
3537
#include "gdscript_workspace.h"
3638

3739
#include "core/io/stream_peer_tcp.h"
3840
#include "core/io/tcp_server.h"
41+
#include "editor/editor_file_system.h"
3942

4043
#include "modules/modules_enabled.gen.h" // For jsonrpc.
4144
#ifdef MODULE_JSONRPC_ENABLED
@@ -47,6 +50,35 @@
4750
#define LSP_MAX_BUFFER_SIZE 4194304
4851
#define LSP_MAX_CLIENTS 8
4952

53+
/**
54+
* Used to load and cache scenes for autocompletion.
55+
* */
56+
class SceneCache {
57+
private:
58+
friend class GDScriptLanguageProtocol;
59+
60+
HashMap<String, Node *> cache;
61+
Ref<GDScriptWorkspace> workspace;
62+
bool is_loading = false;
63+
HashMap<String, List<String>> owners_path_cache; // needed to keep track of requested scenes in the ResourceLoader
64+
Array resource_request_queue;
65+
66+
void _get_owners(EditorFileSystemDirectory *p_efsd, const String &p_path, List<String> &r_owners);
67+
void _set_owner_scene_node(const String &p_path);
68+
void _add_owner_scene_request(String p_path);
69+
void _check_thread_for_cache_update();
70+
71+
public:
72+
bool has(const String &p_path);
73+
Node *get(const String &p_path);
74+
Node *get_for_uri(const String &p_uri);
75+
void set(const String &p_path);
76+
void set_for_uri(const String &p_uri);
77+
void erase(const String &p_path);
78+
void erase_for_uri(const String &p_uri);
79+
void clear();
80+
};
81+
5082
class GDScriptLanguageProtocol : public JSONRPC {
5183
GDCLASS(GDScriptLanguageProtocol, JSONRPC)
5284

@@ -74,6 +106,7 @@ class GDScriptLanguageProtocol : public JSONRPC {
74106
static GDScriptLanguageProtocol *singleton;
75107

76108
HashMap<int, Ref<LSPeer>> clients;
109+
SceneCache scene_cache;
77110
Ref<TCPServer> server;
78111
int latest_client_id = 0;
79112
int next_client_id = 0;
@@ -101,6 +134,8 @@ class GDScriptLanguageProtocol : public JSONRPC {
101134
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
102135
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
103136
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
137+
_FORCE_INLINE_ SceneCache *get_scene_cache() { return &scene_cache; }
138+
104139
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
105140

106141
void poll(int p_limit_usec);

modules/gdscript/language_server/gdscript_text_document.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,12 @@ void GDScriptTextDocument::_bind_methods() {
6565
void GDScriptTextDocument::didOpen(const Variant &p_param) {
6666
lsp::TextDocumentItem doc = load_document_item(p_param);
6767
sync_script_content(doc.uri, doc.text);
68+
GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->set_for_uri(doc.uri);
6869
}
6970

7071
void GDScriptTextDocument::didClose(const Variant &p_param) {
71-
// Left empty on purpose. Godot does nothing special on closing a document,
72-
// but it satisfies LSP clients that require didClose be implemented.
72+
lsp::TextDocumentItem doc = load_document_item(p_param);
73+
GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->erase_for_uri(doc.uri);
7374
}
7475

7576
void GDScriptTextDocument::didChange(const Variant &p_param) {
@@ -82,6 +83,7 @@ void GDScriptTextDocument::didChange(const Variant &p_param) {
8283
doc.text = evt.text;
8384
}
8485
sync_script_content(doc.uri, doc.text);
86+
GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get_for_uri(doc.uri);
8587
}
8688

8789
void GDScriptTextDocument::willSaveWaitUntil(const Variant &p_param) {

0 commit comments

Comments
 (0)