Merge pull request #114559 from HolonProduction/lsp/fix-performance-scene-loading-workspace-completion
LSP: Fix loading scene for every request on script file for workspace completion
This commit is contained in:
commit
56f3e2611d
8 changed files with 276 additions and 58 deletions
|
|
@ -148,6 +148,9 @@ Error GDScriptLanguageProtocol::on_client_connected() {
|
|||
|
||||
void GDScriptLanguageProtocol::on_client_disconnected(const int &p_client_id) {
|
||||
clients.erase(p_client_id);
|
||||
if (clients.is_empty()) {
|
||||
scene_cache.clear();
|
||||
}
|
||||
EditorNode::get_log()->add_message("[LSP] Disconnected", EditorLog::MSG_TYPE_EDITOR);
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +272,8 @@ void GDScriptLanguageProtocol::poll(int p_limit_usec) {
|
|||
on_client_connected();
|
||||
}
|
||||
|
||||
scene_cache.poll();
|
||||
|
||||
HashMap<int, Ref<LSPeer>>::Iterator E = clients.begin();
|
||||
while (E != clients.end()) {
|
||||
Ref<LSPeer> peer = E->value;
|
||||
|
|
@ -315,6 +320,7 @@ void GDScriptLanguageProtocol::stop() {
|
|||
peer->connection->disconnect_from_host();
|
||||
}
|
||||
|
||||
scene_cache.clear();
|
||||
server->stop();
|
||||
}
|
||||
|
||||
|
|
@ -447,6 +453,8 @@ void GDScriptLanguageProtocol::lsp_did_open(const Dictionary &p_params) {
|
|||
|
||||
client->managed_files[path] = document;
|
||||
client->parse_script(path);
|
||||
|
||||
scene_cache.request_load(path);
|
||||
}
|
||||
|
||||
void GDScriptLanguageProtocol::lsp_did_change(const Dictionary &p_params) {
|
||||
|
|
@ -492,6 +500,8 @@ void GDScriptLanguageProtocol::lsp_did_close(const Dictionary &p_params) {
|
|||
|
||||
/// A close notification requires a previous open notification to be sent.
|
||||
ERR_FAIL_COND_MSG(!was_opened, "LSP: Client is closing file without opening it.");
|
||||
|
||||
scene_cache.unload(path);
|
||||
}
|
||||
|
||||
void GDScriptLanguageProtocol::resolve_related_symbols(const LSP::TextDocumentPositionParams &p_doc_pos, List<const LSP::DocumentSymbol *> &r_list) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
|
||||
#include "gdscript_text_document.h"
|
||||
#include "gdscript_workspace.h"
|
||||
#include "scene_cache.h"
|
||||
|
||||
#include "core/io/stream_peer_tcp.h"
|
||||
#include "core/io/tcp_server.h"
|
||||
|
|
@ -92,6 +93,7 @@ private:
|
|||
static GDScriptLanguageProtocol *singleton;
|
||||
|
||||
HashMap<int, Ref<LSPeer>> clients;
|
||||
SceneCache scene_cache;
|
||||
Ref<TCPServer> server;
|
||||
int latest_client_id = LSP_NO_CLIENT;
|
||||
int next_client_id = 0;
|
||||
|
|
@ -119,6 +121,8 @@ public:
|
|||
_FORCE_INLINE_ static GDScriptLanguageProtocol *get_singleton() { return singleton; }
|
||||
_FORCE_INLINE_ Ref<GDScriptWorkspace> get_workspace() { return workspace; }
|
||||
_FORCE_INLINE_ Ref<GDScriptTextDocument> get_text_document() { return text_document; }
|
||||
_FORCE_INLINE_ SceneCache *get_scene_cache() { return &scene_cache; }
|
||||
|
||||
_FORCE_INLINE_ bool is_initialized() const { return _initialized; }
|
||||
|
||||
void poll(int p_limit_usec);
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
#include "editor/editor_node.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
void GDScriptWorkspace::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("apply_new_signal"), &GDScriptWorkspace::apply_new_signal);
|
||||
|
|
@ -592,51 +591,6 @@ void GDScriptWorkspace::publish_diagnostics(const String &p_path) {
|
|||
GDScriptLanguageProtocol::get_singleton()->notify_client("textDocument/publishDiagnostics", params);
|
||||
}
|
||||
|
||||
void GDScriptWorkspace::_get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners) {
|
||||
if (!efsd) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_subdir_count(); i++) {
|
||||
_get_owners(efsd->get_subdir(i), p_path, owners);
|
||||
}
|
||||
|
||||
for (int i = 0; i < efsd->get_file_count(); i++) {
|
||||
Vector<String> deps = efsd->get_file_deps(i);
|
||||
bool found = false;
|
||||
for (int j = 0; j < deps.size(); j++) {
|
||||
if (deps[j] == p_path) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
continue;
|
||||
}
|
||||
|
||||
owners.push_back(efsd->get_file_path(i));
|
||||
}
|
||||
}
|
||||
|
||||
Node *GDScriptWorkspace::_get_owner_scene_node(String p_path) {
|
||||
Node *owner_scene_node = nullptr;
|
||||
List<String> owners;
|
||||
|
||||
_get_owners(EditorFileSystem::get_singleton()->get_filesystem(), p_path, owners);
|
||||
|
||||
for (const String &owner : owners) {
|
||||
NodePath owner_path = owner;
|
||||
Ref<Resource> owner_res = ResourceLoader::load(String(owner_path));
|
||||
if (Object::cast_to<PackedScene>(owner_res.ptr())) {
|
||||
Ref<PackedScene> owner_packed_scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*owner_res));
|
||||
owner_scene_node = owner_packed_scene->instantiate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return owner_scene_node;
|
||||
}
|
||||
|
||||
void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<ScriptLanguage::CodeCompletionOption> *r_options) {
|
||||
String path = get_file_path(p_params.textDocument.uri);
|
||||
String call_hint;
|
||||
|
|
@ -644,7 +598,7 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
|
|||
|
||||
const ExtendGDScriptParser *parser = GDScriptLanguageProtocol::get_singleton()->get_parse_result(path);
|
||||
if (parser) {
|
||||
Node *owner_scene_node = _get_owner_scene_node(path);
|
||||
Node *owner_scene_node = GDScriptLanguageProtocol::get_singleton()->get_scene_cache()->get(path);
|
||||
|
||||
Array stack;
|
||||
Node *current = nullptr;
|
||||
|
|
@ -670,9 +624,6 @@ void GDScriptWorkspace::completion(const LSP::CompletionParams &p_params, List<S
|
|||
|
||||
String code = parser->get_text_for_completion(p_params.position);
|
||||
GDScriptLanguage::get_singleton()->complete_code(code, path, current, r_options, forced, call_hint);
|
||||
if (owner_scene_node) {
|
||||
memdelete(owner_scene_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,15 +35,11 @@
|
|||
#include "godot_lsp.h"
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
|
||||
class GDScriptWorkspace : public RefCounted {
|
||||
GDCLASS(GDScriptWorkspace, RefCounted);
|
||||
|
||||
private:
|
||||
void _get_owners(EditorFileSystemDirectory *efsd, String p_path, List<String> &owners);
|
||||
Node *_get_owner_scene_node(String p_path);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
void didDeleteFiles() {}
|
||||
Error parse_script(const String &p_path, const String &p_content) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,15 @@
|
|||
#include "core/object/class_db.h"
|
||||
#include "core/templates/list.h"
|
||||
|
||||
// Enable additional LSP related logging.
|
||||
//#define DEBUG_LSP
|
||||
|
||||
#ifdef DEBUG_LSP
|
||||
#define LOG_LSP(...) print_line("[ LSP -", __FILE__, ":", __LINE__, "-", __func__, "] -", ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_LSP(...)
|
||||
#endif
|
||||
|
||||
namespace LSP {
|
||||
|
||||
typedef String DocumentUri;
|
||||
|
|
|
|||
186
modules/gdscript/language_server/scene_cache.cpp
Normal file
186
modules/gdscript/language_server/scene_cache.cpp
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/**************************************************************************/
|
||||
/* scene_cache.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "scene_cache.h"
|
||||
|
||||
#include "godot_lsp.h"
|
||||
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
#include "scene/resources/packed_scene.h"
|
||||
|
||||
void SceneCache::_get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths) {
|
||||
if (!p_dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
|
||||
_get_owner_paths(p_dir->get_subdir(i), p_script_path, r_owner_paths);
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_dir->get_file_count(); i++) {
|
||||
if (p_dir->get_file_deps(i).has(p_script_path)) {
|
||||
r_owner_paths.push_back(p_dir->get_file_path(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SceneCache::_finalize_scene_load() {
|
||||
ERR_FAIL_COND(current_loaded_owner.is_empty() || script_path_queue.is_empty());
|
||||
|
||||
Ref<PackedScene> scene_res = ResourceLoader::load_threaded_get(current_loaded_owner);
|
||||
|
||||
if (scene_res.is_valid()) {
|
||||
cache[script_path_queue[0]] = scene_res->instantiate();
|
||||
} else {
|
||||
cache[script_path_queue[0]] = nullptr;
|
||||
}
|
||||
|
||||
LOG_LSP("Scene cached for script:", script_path_queue[0]);
|
||||
LOG_LSP("pending_script_queue length:", script_path_queue.size() - 1);
|
||||
|
||||
script_path_queue.remove_at(0);
|
||||
current_loaded_owner = String();
|
||||
}
|
||||
|
||||
void SceneCache::poll() {
|
||||
if (current_loaded_owner.is_empty()) {
|
||||
// No load ongoing, start the next one.
|
||||
|
||||
if (EditorFileSystem::get_singleton()->is_scanning() || script_path_queue.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocalVector<String> owners;
|
||||
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), script_path_queue[0], owners);
|
||||
for (const String &owner : owners) {
|
||||
if (ResourceLoader::load_threaded_request(owner) == Error::OK) {
|
||||
current_loaded_owner = owner;
|
||||
LOG_LSP("Scene load started for:", current_loaded_owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_loaded_owner.is_empty()) {
|
||||
cache[script_path_queue[0]] = nullptr;
|
||||
LOG_LSP("No scene found for script:", script_path_queue[0]);
|
||||
script_path_queue.remove_at(0);
|
||||
LOG_LSP("pending_script_queue length:", script_path_queue.size());
|
||||
}
|
||||
} else {
|
||||
ERR_FAIL_COND(script_path_queue.is_empty());
|
||||
|
||||
// There is an ongoing load. Check the status.
|
||||
|
||||
ResourceLoader::ThreadLoadStatus status = ResourceLoader::load_threaded_get_status(current_loaded_owner);
|
||||
|
||||
if (status == ResourceLoader::THREAD_LOAD_IN_PROGRESS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == ResourceLoader::THREAD_LOAD_LOADED) {
|
||||
_finalize_scene_load();
|
||||
} else {
|
||||
LOG_LSP("Scene load failure for:", current_loaded_owner);
|
||||
cache[script_path_queue[0]] = nullptr;
|
||||
|
||||
script_path_queue.remove_at(0);
|
||||
current_loaded_owner = String();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node *SceneCache::get(const String &p_script_path) {
|
||||
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
|
||||
_finalize_scene_load();
|
||||
} else {
|
||||
script_path_queue.erase(p_script_path);
|
||||
}
|
||||
|
||||
if (Node **entry = cache.getptr(p_script_path)) {
|
||||
return *entry;
|
||||
}
|
||||
|
||||
// Fallback to blocking load. This could happen if the open request was only recently sent.
|
||||
// TODO: This could also happen when multiple clients are connected.
|
||||
|
||||
LocalVector<String> owners;
|
||||
_get_owner_paths(EditorFileSystem::get_singleton()->get_filesystem(), p_script_path, owners);
|
||||
for (const String &owner : owners) {
|
||||
Ref<PackedScene> scene = ResourceLoader::load(owner);
|
||||
if (scene.is_valid()) {
|
||||
Node *instance = scene->instantiate();
|
||||
cache[p_script_path] = instance;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
cache[p_script_path] = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SceneCache::request_load(const String &p_script_path) {
|
||||
if (!cache.has(p_script_path) && !script_path_queue.has(p_script_path)) {
|
||||
script_path_queue.push_back(p_script_path);
|
||||
LOG_LSP("Scene load requested for:", p_script_path);
|
||||
LOG_LSP("pending_script_queue length:", script_path_queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
void SceneCache::unload(const String &p_script_path) {
|
||||
if (!script_path_queue.is_empty() && script_path_queue[0] == p_script_path && !current_loaded_owner.is_empty()) {
|
||||
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
|
||||
|
||||
script_path_queue.remove_at(0);
|
||||
current_loaded_owner = String();
|
||||
} else {
|
||||
script_path_queue.erase(p_script_path);
|
||||
}
|
||||
|
||||
if (!cache.has(p_script_path)) {
|
||||
return;
|
||||
}
|
||||
memdelete_notnull(cache[p_script_path]);
|
||||
cache.erase(p_script_path);
|
||||
LOG_LSP("Cache cleared for path:", p_script_path);
|
||||
}
|
||||
|
||||
void SceneCache::clear() {
|
||||
if (!current_loaded_owner.is_empty()) {
|
||||
_ALLOW_DISCARD_ ResourceLoader::load_threaded_get(current_loaded_owner);
|
||||
current_loaded_owner = String();
|
||||
}
|
||||
script_path_queue.clear();
|
||||
for (const KeyValue<String, Node *> &E : cache) {
|
||||
memdelete_notnull(E.value);
|
||||
}
|
||||
cache.clear();
|
||||
LOG_LSP("Cache cleared.");
|
||||
}
|
||||
65
modules/gdscript/language_server/scene_cache.h
Normal file
65
modules/gdscript/language_server/scene_cache.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/**************************************************************************/
|
||||
/* scene_cache.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
class Node;
|
||||
class EditorFileSystemDirectory;
|
||||
class PackedScene;
|
||||
|
||||
/**
|
||||
* Used to load and cache scene instances for LSP autocompletion.
|
||||
*
|
||||
* This implementation is not thread safe.
|
||||
*/
|
||||
class SceneCache {
|
||||
// Always contains the path to the scene which is currently loaded via the `ResourceLoader`.
|
||||
// If this is not empty, `script_path_queue` must have at least one element.
|
||||
String current_loaded_owner;
|
||||
LocalVector<String> script_path_queue;
|
||||
|
||||
HashMap<String, Node *> cache;
|
||||
|
||||
void _get_owner_paths(EditorFileSystemDirectory *p_dir, const String &p_script_path, LocalVector<String> &r_owner_paths);
|
||||
void _finalize_scene_load();
|
||||
|
||||
public:
|
||||
void poll();
|
||||
|
||||
void clear();
|
||||
void request_load(const String &p_script_path);
|
||||
void unload(const String &p_script_path);
|
||||
|
||||
Node *get(const String &p_script_path);
|
||||
};
|
||||
|
|
@ -42,10 +42,7 @@
|
|||
#include "../language_server/godot_lsp.h"
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access_pack.h"
|
||||
#include "core/os/os.h"
|
||||
#include "editor/doc/editor_help.h"
|
||||
#include "editor/editor_node.h"
|
||||
#include "editor/file_system/editor_file_system.h"
|
||||
|
||||
#include "modules/gdscript/gdscript_analyzer.h"
|
||||
#include "modules/regex/regex.h"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue