Skip to content
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

Optimize scanning routines in the project manager #86271

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 89 additions & 104 deletions editor/project_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Comment on lines +1252 to 1254
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but these should be StringNames (probably not static then).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I tried making them static StringNames, but it didn't quite work. I didn't want to spend time adding the usual framework to make them accessible outside of the class with this PR, so I just followed the existing pattern. But I will rework this in a future PR. We probably can have a "project manager string names" file, like we do with other contexts.


Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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));
}
Expand All @@ -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.
Expand Down Expand Up @@ -2183,15 +2230,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());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure why we used to do this before updating the project list. Changing the filter already sets the search term, and during the first scan we don't have any search term anyway, it's all empty.

So this bit I simply removed even before removing the method itself. I tested and didn't see issues. The search term was still applied after scanning and importing dropped folders.

_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;
Expand Down Expand Up @@ -2429,30 +2467,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();
}
Expand Down Expand Up @@ -2652,54 +2666,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);
}

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);
}
}
}
ERR_FAIL_COND(folders_set.size() == 0); // This can't really happen, we consume every dropped file path above.

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) {
Expand Down Expand Up @@ -2740,11 +2726,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);
Expand Down Expand Up @@ -2870,8 +2851,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);
Expand Down Expand Up @@ -2950,10 +2931,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);
Expand Down Expand Up @@ -3099,7 +3081,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"));
Expand Down Expand Up @@ -3133,10 +3115,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));
Expand Down Expand Up @@ -3245,29 +3223,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();

Ref<DirAccess> dir_access = DirAccess::create(DirAccess::AccessType::ACCESS_FILESYSTEM);
// Initialize project list.
{
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 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 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);
if (error != OK) {
ERR_PRINT("Could not create project autoscan directory at: " + autoscan_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));
Expand Down
15 changes: 7 additions & 8 deletions editor/project_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ class ProjectList : public ScrollContainer {
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);
Expand All @@ -288,12 +291,15 @@ class ProjectList : public ScrollContainer {
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);
Expand All @@ -316,7 +322,6 @@ class ProjectList : public ScrollContainer {
void set_order_option(int p_option);

void update_dock_menu();
void migrate_config();
void save_config();

ProjectList();
Expand Down Expand Up @@ -367,7 +372,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;
Expand Down Expand Up @@ -423,12 +427,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);

Expand All @@ -447,7 +447,6 @@ class ProjectManager : public Control {

protected:
void _notification(int p_what);
static void _bind_methods();

public:
static ProjectManager *get_singleton() { return singleton; }
Expand Down
Loading