Merge pull request #86271 from YuriSizov/pm-optimize-multiple-ways-of-scanning

Optimize scanning routines in the project manager
This commit is contained in:
Yuri Sizov 2023-12-20 15:08:06 +01:00
commit 5f641541ea
2 changed files with 97 additions and 113 deletions

View file

@ -1249,6 +1249,7 @@ struct ProjectListComparator {
}
};
const char *ProjectList::SIGNAL_LIST_CHANGED = "list_changed";
const char *ProjectList::SIGNAL_SELECTION_CHANGED = "selection_changed";
const char *ProjectList::SIGNAL_PROJECT_ASK_OPEN = "project_ask_open";
@ -1356,7 +1357,7 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
return Item(project_name, description, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
}
void ProjectList::migrate_config() {
void ProjectList::_migrate_config() {
// Proposal #1637 moved the project list from editor settings to a separate config file
// If the new config file doesn't exist, populate it from EditorSettings
if (FileAccess::exists(_config_path)) {
@ -1388,9 +1389,10 @@ void ProjectList::migrate_config() {
save_config();
}
void ProjectList::load_projects() {
void ProjectList::update_project_list() {
// This is a full, hard reload of the list. Don't call this unless really required, it's expensive.
// If you have 150 projects, it may read through 150 files on your disk at once + load 150 icons.
// FIXME: Does it really have to be a full, hard reload? Runtime updates should be made much cheaper.
// Clear whole list
for (int i = 0; i < _projects.size(); ++i) {
@ -1421,6 +1423,7 @@ void ProjectList::load_projects() {
update_dock_menu();
set_v_scroll(0);
emit_signal(SNAME(SIGNAL_LIST_CHANGED));
}
void ProjectList::update_dock_menu() {
@ -1678,6 +1681,48 @@ void ProjectList::erase_missing_projects() {
save_config();
}
void ProjectList::_scan_folder_recursive(const String &p_path, List<String> *r_projects) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error error = da->change_dir(p_path);
ERR_FAIL_COND_MSG(error != OK, vformat("Failed to open the path \"%s\" for scanning (code %d).", p_path, error));
da->list_dir_begin();
String n = da->get_next();
while (!n.is_empty()) {
if (da->current_is_dir() && n[0] != '.') {
_scan_folder_recursive(da->get_current_dir().path_join(n), r_projects);
} else if (n == "project.godot") {
r_projects->push_back(da->get_current_dir());
}
n = da->get_next();
}
da->list_dir_end();
}
void ProjectList::find_projects(const String &p_path) {
PackedStringArray paths = { p_path };
find_projects_multiple(paths);
}
void ProjectList::find_projects_multiple(const PackedStringArray &p_paths) {
List<String> projects;
for (int i = 0; i < p_paths.size(); i++) {
const String &base_path = p_paths.get(i);
print_verbose(vformat("Scanning for projects in \"%s\".", base_path));
_scan_folder_recursive(base_path, &projects);
print_verbose(vformat("Found %d project(s).", projects.size()));
}
for (const String &E : projects) {
add_project(E, false);
}
save_config();
update_project_list();
}
int ProjectList::refresh_project(const String &dir_path) {
// Reloads information about a specific project.
// If it wasn't loaded and should be in the list, it is added (i.e new project).
@ -1916,6 +1961,7 @@ void ProjectList::_show_project(const String &p_path) {
}
void ProjectList::_bind_methods() {
ADD_SIGNAL(MethodInfo(SIGNAL_LIST_CHANGED));
ADD_SIGNAL(MethodInfo(SIGNAL_SELECTION_CHANGED));
ADD_SIGNAL(MethodInfo(SIGNAL_PROJECT_ASK_OPEN));
}
@ -1926,6 +1972,7 @@ ProjectList::ProjectList() {
add_child(_scroll_children);
_config_path = EditorPaths::get_singleton()->get_data_dir().path_join("projects.cfg");
_migrate_config();
}
/// Project Manager.
@ -2213,15 +2260,6 @@ void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {
}
}
void ProjectManager::_load_recent_projects() {
_project_list->set_search_term(search_box->get_text().strip_edges());
_project_list->load_projects();
_update_project_buttons();
tabs->set_current_tab(0);
}
void ProjectManager::_on_projects_updated() {
Vector<ProjectList::Item> selected_projects = _project_list->get_selected_projects();
int index = 0;
@ -2459,30 +2497,6 @@ void ProjectManager::_run_project() {
}
}
void ProjectManager::_scan_dir(const String &path) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
Error error = da->change_dir(path);
ERR_FAIL_COND_MSG(error != OK, "Could not scan directory at: " + path);
da->list_dir_begin();
String n = da->get_next();
while (!n.is_empty()) {
if (da->current_is_dir() && !n.begins_with(".")) {
_scan_dir(da->get_current_dir().path_join(n));
} else if (n == "project.godot") {
_project_list->add_project(da->get_current_dir(), false);
}
n = da->get_next();
}
da->list_dir_end();
}
void ProjectManager::_scan_begin(const String &p_base) {
print_line("Scanning projects at: " + p_base);
_scan_dir(p_base);
_project_list->save_config();
_load_recent_projects();
}
void ProjectManager::_scan_projects() {
scan_dir->popup_file_dialog();
}
@ -2682,54 +2696,26 @@ void ProjectManager::_install_project(const String &p_zip_path, const String &p_
}
void ProjectManager::_files_dropped(PackedStringArray p_files) {
// TODO: Support installing multiple ZIPs at the same time?
if (p_files.size() == 1 && p_files[0].ends_with(".zip")) {
const String file = p_files[0].get_file();
_install_project(p_files[0], file.substr(0, file.length() - 4).capitalize());
const String &file = p_files[0];
_install_project(file, file.get_file().get_basename().capitalize());
return;
}
HashSet<String> folders_set;
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
for (int i = 0; i < p_files.size(); i++) {
String file = p_files[i];
const String &file = p_files[i];
folders_set.insert(da->dir_exists(file) ? file : file.get_base_dir());
}
if (folders_set.size() > 0) {
PackedStringArray folders;
for (const String &E : folders_set) {
folders.push_back(E);
}
ERR_FAIL_COND(folders_set.size() == 0); // This can't really happen, we consume every dropped file path above.
bool confirm = true;
if (folders.size() == 1) {
Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (dir->change_dir(folders[0]) == OK) {
dir->list_dir_begin();
String file = dir->get_next();
while (confirm && !file.is_empty()) {
if (!dir->current_is_dir() && file.ends_with("project.godot")) {
confirm = false;
}
file = dir->get_next();
}
dir->list_dir_end();
}
}
if (confirm) {
multi_scan_ask->get_ok_button()->disconnect("pressed", callable_mp(this, &ProjectManager::_scan_multiple_folders));
multi_scan_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_scan_multiple_folders).bind(folders));
multi_scan_ask->set_text(
vformat(TTR("Are you sure to scan %s folders for existing Godot projects?\nThis could take a while."), folders.size()));
multi_scan_ask->popup_centered();
} else {
_scan_multiple_folders(folders);
}
}
}
void ProjectManager::_scan_multiple_folders(PackedStringArray p_files) {
for (int i = 0; i < p_files.size(); i++) {
_scan_begin(p_files.get(i));
PackedStringArray folders;
for (const String &E : folders_set) {
folders.push_back(E);
}
_project_list->find_projects_multiple(folders);
}
void ProjectManager::_on_order_option_changed(int p_idx) {
@ -2770,11 +2756,6 @@ void ProjectManager::_on_search_term_submitted(const String &p_text) {
_open_selected_projects_ask();
}
void ProjectManager::_bind_methods() {
ClassDB::bind_method("_update_project_buttons", &ProjectManager::_update_project_buttons);
ClassDB::bind_method("_version_button_pressed", &ProjectManager::_version_button_pressed);
}
void ProjectManager::_open_asset_library() {
asset_library->disable_community_support();
tabs->set_current_tab(1);
@ -2900,8 +2881,8 @@ ProjectManager::ProjectManager() {
vb->add_child(center_box);
tabs = memnew(TabContainer);
center_box->add_child(tabs);
tabs->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT);
center_box->add_child(tabs);
tabs->connect("tab_changed", callable_mp(this, &ProjectManager::_on_tab_changed));
local_projects_vb = memnew(VBoxContainer);
@ -2980,10 +2961,11 @@ ProjectManager::ProjectManager() {
search_tree_hb->add_child(search_panel);
_project_list = memnew(ProjectList);
_project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
_project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));
_project_list->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
search_panel->add_child(_project_list);
_project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
_project_list->connect(ProjectList::SIGNAL_SELECTION_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
_project_list->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_ask));
// The side bar with the edit, run, rename, etc. buttons.
VBoxContainer *tree_vb = memnew(VBoxContainer);
@ -3129,7 +3111,7 @@ ProjectManager::ProjectManager() {
scan_dir->set_title(TTR("Select a Folder to Scan")); // must be after mode or it's overridden
scan_dir->set_current_dir(EDITOR_GET("filesystem/directories/default_project_path"));
add_child(scan_dir);
scan_dir->connect("dir_selected", callable_mp(this, &ProjectManager::_scan_begin));
scan_dir->connect("dir_selected", callable_mp(_project_list, &ProjectList::find_projects));
erase_missing_ask = memnew(ConfirmationDialog);
erase_missing_ask->set_ok_button_text(TTR("Remove All"));
@ -3163,10 +3145,6 @@ ProjectManager::ProjectManager() {
multi_run_ask->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_run_project_confirm));
add_child(multi_run_ask);
multi_scan_ask = memnew(ConfirmationDialog);
multi_scan_ask->set_ok_button_text(TTR("Scan"));
add_child(multi_scan_ask);
ask_update_settings = memnew(ConfirmationDialog);
ask_update_settings->set_autowrap(true);
ask_update_settings->get_ok_button()->connect("pressed", callable_mp(this, &ProjectManager::_confirm_update_settings));
@ -3275,29 +3253,36 @@ ProjectManager::ProjectManager() {
create_tag_btn->connect("pressed", callable_mp((Window *)create_tag_dialog, &Window::popup_centered).bind(Vector2i(500, 0) * EDSCALE));
}
_project_list->migrate_config();
_load_recent_projects();
// Initialize project list.
{
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
if (!dir_access->dir_exists(default_project_path)) {
Error error = dir_access->make_dir_recursive(default_project_path);
if (error != OK) {
ERR_PRINT("Could not create default project directory at: " + default_project_path);
}
}
String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
if (!autoscan_path.is_empty()) {
if (dir_access->dir_exists(autoscan_path)) {
_scan_begin(autoscan_path);
} else {
Error error = dir_access->make_dir_recursive(autoscan_path);
String default_project_path = EDITOR_GET("filesystem/directories/default_project_path");
if (!default_project_path.is_empty() && !dir_access->dir_exists(default_project_path)) {
Error error = dir_access->make_dir_recursive(default_project_path);
if (error != OK) {
ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);
ERR_PRINT("Could not create default project directory at: " + default_project_path);
}
}
bool scanned_for_projects = false; // Scanning will update the list automatically.
String autoscan_path = EDITOR_GET("filesystem/directories/autoscan_project_path");
if (!autoscan_path.is_empty()) {
if (dir_access->dir_exists(autoscan_path)) {
_project_list->find_projects(autoscan_path);
scanned_for_projects = true;
} else {
Error error = dir_access->make_dir_recursive(autoscan_path);
if (error != OK) {
ERR_PRINT("Could not create project autoscan directory at: " + autoscan_path);
}
}
}
if (!scanned_for_projects) {
_project_list->update_project_list();
}
}
SceneTree::get_singleton()->get_root()->connect("files_dropped", callable_mp(this, &ProjectManager::_files_dropped));

View file

@ -267,6 +267,9 @@ private:
void _favorite_pressed(Node *p_hb);
void _show_project(const String &p_path);
void _migrate_config();
void _scan_folder_recursive(const String &p_path, List<String> *r_projects);
void _clear_project_selection();
void _toggle_project(int p_index);
void _select_project_nocheck(int p_index);
@ -288,12 +291,15 @@ protected:
static void _bind_methods();
public:
static const char *SIGNAL_LIST_CHANGED;
static const char *SIGNAL_SELECTION_CHANGED;
static const char *SIGNAL_PROJECT_ASK_OPEN;
void load_projects();
void update_project_list();
int get_project_count() const;
void find_projects(const String &p_path);
void find_projects_multiple(const PackedStringArray &p_paths);
void sort_projects();
void add_project(const String &dir_path, bool favorite);
@ -316,7 +322,6 @@ public:
void set_order_option(int p_option);
void update_dock_menu();
void migrate_config();
void save_config();
ProjectList();
@ -369,7 +374,6 @@ class ProjectManager : public Control {
ConfirmationDialog *erase_missing_ask = nullptr;
ConfirmationDialog *multi_open_ask = nullptr;
ConfirmationDialog *multi_run_ask = nullptr;
ConfirmationDialog *multi_scan_ask = nullptr;
ConfirmationDialog *ask_full_convert_dialog = nullptr;
ConfirmationDialog *ask_update_settings = nullptr;
ConfirmationDialog *open_templates = nullptr;
@ -425,12 +429,8 @@ class ProjectManager : public Control {
void _set_new_tag_name(const String p_name);
void _create_new_tag();
void _load_recent_projects();
void _on_project_created(const String &dir);
void _on_projects_updated();
void _scan_multiple_folders(PackedStringArray p_files);
void _scan_begin(const String &p_base);
void _scan_dir(const String &path);
void _install_project(const String &p_zip_path, const String &p_title);
@ -449,7 +449,6 @@ class ProjectManager : public Control {
protected:
void _notification(int p_what);
static void _bind_methods();
public:
static ProjectManager *get_singleton() { return singleton; }