Implement a "Recovery Mode" for recovering crashing/hanging projects during initialization

This commit is contained in:
Ricardo Subtil
2024-04-30 21:13:10 +01:00
parent bdf625bd54
commit b77aa473a1
34 changed files with 484 additions and 96 deletions

View File

@ -244,6 +244,7 @@ void ProjectManager::_update_theme(bool p_skip_creation) {
import_btn->set_button_icon(get_editor_theme_icon(SNAME("Load")));
scan_btn->set_button_icon(get_editor_theme_icon(SNAME("Search")));
open_btn->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
open_options_btn->set_button_icon(get_editor_theme_icon(SNAME("Collapse")));
run_btn->set_button_icon(get_editor_theme_icon(SNAME("Play")));
rename_btn->set_button_icon(get_editor_theme_icon(SNAME("Rename")));
manage_tags_btn->set_button_icon(get_editor_theme_icon("Script"));
@ -263,6 +264,9 @@ void ProjectManager::_update_theme(bool p_skip_creation) {
manage_tags_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
erase_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
erase_missing_btn->add_theme_constant_override("h_separation", get_theme_constant(SNAME("sidebar_button_icon_separation"), SNAME("ProjectManager")));
open_btn_container->add_theme_constant_override("separation", 0);
open_options_popup->set_item_icon(0, get_editor_theme_icon(SNAME("NodeWarning")));
}
// Asset library popup.
@ -495,6 +499,10 @@ void ProjectManager::_open_selected_projects() {
args.push_back("--editor");
if (open_in_recovery_mode) {
args.push_back("--recovery-mode");
}
Error err = OS::get_singleton()->create_instance(args);
if (err != OK) {
loading_label->hide();
@ -510,7 +518,7 @@ void ProjectManager::_open_selected_projects() {
get_tree()->quit();
}
void ProjectManager::_open_selected_projects_ask() {
void ProjectManager::_open_selected_projects_check_warnings() {
const HashSet<String> &selected_list = project_list->get_selected_project_keys();
if (selected_list.size() < 1) {
return;
@ -599,6 +607,22 @@ void ProjectManager::_open_selected_projects_ask() {
_open_selected_projects();
}
void ProjectManager::_open_selected_projects_check_recovery_mode() {
ProjectList::Item project = project_list->get_selected_projects()[0];
if (project.missing) {
return;
}
open_in_recovery_mode = false;
// Check if the project failed to load during last startup.
if (project.recovery_mode) {
_open_recovery_mode_ask(false);
return;
}
_open_selected_projects_check_warnings();
}
void ProjectManager::_open_selected_projects_with_migration() {
#ifndef DISABLE_DEPRECATED
if (project_list->get_selected_projects().size() == 1) {
@ -691,6 +715,7 @@ void ProjectManager::_update_project_buttons() {
erase_btn->set_disabled(empty_selection);
open_btn->set_disabled(empty_selection || is_missing_project_selected);
open_options_btn->set_disabled(empty_selection || is_missing_project_selected);
rename_btn->set_disabled(empty_selection || is_missing_project_selected);
manage_tags_btn->set_disabled(empty_selection || is_missing_project_selected || selected_projects.size() > 1);
run_btn->set_disabled(empty_selection || is_missing_project_selected);
@ -698,6 +723,38 @@ void ProjectManager::_update_project_buttons() {
erase_missing_btn->set_disabled(!project_list->is_any_project_missing());
}
void ProjectManager::_open_options_popup() {
Rect2 rect = open_btn_container->get_screen_rect();
rect.position.y += rect.size.height;
open_options_popup->set_size(Size2(rect.size.width, 0));
open_options_popup->set_position(rect.position);
open_options_popup->popup();
}
void ProjectManager::_open_recovery_mode_ask(bool manual) {
String recovery_mode_details;
// Only show the initial crash preamble if this popup wasn't manually triggered.
if (!manual) {
recovery_mode_details +=
TTR("It looks like Godot crashed when opening this project the last time. If you're having problems editing this project, you can try to open it in Recovery Mode.") +
String::utf8("\n\n");
}
recovery_mode_details +=
TTR("Recovery Mode is a special mode that may help to recover projects that crash the engine during initialization. This mode temporarily disables the following features:") +
String::utf8("\n\n") + TTR("Tool scripts") +
String::utf8("\n") + TTR("Editor plugins") +
String::utf8("\n") + TTR("GDExtension addons") +
String::utf8("\n") + TTR("Automatic scene restoring") +
String::utf8("\n\n") + TTR("This mode is intended only for basic editing to troubleshoot such issues, and therefore it will not be possible to run the project during this mode. It is also a good idea to make a backup of your project before proceeding.") +
String::utf8("\n\n") + TTR("Edit the project in Recovery Mode?");
open_recovery_mode_ask->set_text(recovery_mode_details);
open_recovery_mode_ask->popup_centered(Size2(550, 70) * EDSCALE);
}
void ProjectManager::_on_projects_updated() {
Vector<ProjectList::Item> selected_projects = project_list->get_selected_projects();
int index = 0;
@ -711,6 +768,25 @@ void ProjectManager::_on_projects_updated() {
project_list->update_dock_menu();
}
void ProjectManager::_on_open_options_selected(int p_option) {
switch (p_option) {
case 0: // Edit in recovery mode.
_open_recovery_mode_ask(true);
break;
}
}
void ProjectManager::_on_recovery_mode_popup_open_normal() {
open_recovery_mode_ask->hide();
open_in_recovery_mode = false;
_open_selected_projects_check_warnings();
}
void ProjectManager::_on_recovery_mode_popup_open_recovery() {
open_in_recovery_mode = true;
_open_selected_projects_check_warnings();
}
void ProjectManager::_on_project_created(const String &dir, bool edit) {
project_list->add_project(dir, false);
project_list->save_config();
@ -723,7 +799,7 @@ void ProjectManager::_on_project_created(const String &dir, bool edit) {
_update_list_placeholder();
if (edit) {
_open_selected_projects_ask();
_open_selected_projects_check_warnings();
}
project_list->update_dock_menu();
@ -751,7 +827,7 @@ void ProjectManager::_on_search_term_submitted(const String &p_text) {
return;
}
_open_selected_projects_ask();
_open_selected_projects_check_recovery_mode();
}
LineEdit *ProjectManager::get_search_box() {
@ -968,7 +1044,7 @@ void ProjectManager::shortcut_input(const Ref<InputEvent> &p_ev) {
switch (k->get_keycode()) {
case Key::ENTER: {
_open_selected_projects_ask();
_open_selected_projects_check_recovery_mode();
} break;
case Key::HOME: {
if (project_list->get_project_count() > 0) {
@ -1322,7 +1398,7 @@ ProjectManager::ProjectManager() {
project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_project_buttons));
project_list->connect(ProjectList::SIGNAL_LIST_CHANGED, callable_mp(this, &ProjectManager::_update_list_placeholder));
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->connect(ProjectList::SIGNAL_PROJECT_ASK_OPEN, callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));
// Empty project list placeholder.
{
@ -1381,11 +1457,30 @@ ProjectManager::ProjectManager() {
project_list_sidebar->add_child(memnew(HSeparator));
open_btn_container = memnew(HBoxContainer);
open_btn_container->set_anchors_preset(Control::PRESET_FULL_RECT);
project_list_sidebar->add_child(open_btn_container);
open_btn = memnew(Button);
open_btn->set_text(TTR("Edit"));
open_btn->set_shortcut(ED_SHORTCUT("project_manager/edit_project", TTRC("Edit Project"), KeyModifierMask::CMD_OR_CTRL | Key::E));
open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_ask));
project_list_sidebar->add_child(open_btn);
open_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_check_recovery_mode));
open_btn->set_h_size_flags(Control::SIZE_EXPAND_FILL);
open_btn_container->add_child(open_btn);
open_btn_container->add_child(memnew(VSeparator));
open_options_btn = memnew(Button);
open_options_btn->set_icon_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
open_options_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_options_popup));
open_btn_container->add_child(open_options_btn);
open_options_popup = memnew(PopupMenu);
open_options_popup->add_item(TTR("Edit in recovery mode"));
open_options_popup->connect(SceneStringName(id_pressed), callable_mp(this, &ProjectManager::_on_open_options_selected));
open_options_btn->add_child(open_options_popup);
open_btn_container->set_custom_minimum_size(Size2(120, open_btn->get_combined_minimum_size().y));
run_btn = memnew(Button);
run_btn->set_text(TTR("Run"));
@ -1501,6 +1596,14 @@ ProjectManager::ProjectManager() {
multi_run_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_run_project_confirm));
add_child(multi_run_ask);
open_recovery_mode_ask = memnew(ConfirmationDialog);
open_recovery_mode_ask->set_min_size(Size2(550, 70) * EDSCALE);
open_recovery_mode_ask->set_autowrap(true);
open_recovery_mode_ask->add_button(TTR("Edit normally"))->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_normal));
open_recovery_mode_ask->set_ok_button_text(TTR("Edit in Recovery Mode"));
open_recovery_mode_ask->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_on_recovery_mode_popup_open_recovery));
add_child(open_recovery_mode_ask);
ask_update_settings = memnew(ConfirmationDialog);
ask_update_settings->set_autowrap(true);
ask_update_settings->get_ok_button()->connect(SceneStringName(pressed), callable_mp(this, &ProjectManager::_open_selected_projects_with_migration));