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

@ -87,6 +87,7 @@ private:
bool project_manager_hint = false;
bool extension_reloading = false;
bool embedded_in_editor = false;
bool recovery_mode_hint = false;
bool _print_header = true;
@ -162,6 +163,9 @@ public:
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) { extension_reloading = p_enabled; }
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return extension_reloading; }
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) { recovery_mode_hint = p_enabled; }
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return recovery_mode_hint; }
#else
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_editor_hint() const { return false; }
@ -171,6 +175,9 @@ public:
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) {}
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return false; }
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return false; }
#endif
Dictionary get_version_info() const;

View File

@ -84,6 +84,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co
}
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
Ref<GDExtensionLibraryLoader> loader;
loader.instantiate();
return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader);
@ -119,6 +123,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
#else
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
@ -161,6 +169,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
}
GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return LOAD_STATUS_FAILED;
}
if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
@ -207,6 +219,10 @@ String GDExtensionManager::class_get_icon_path(const String &p_class) const {
}
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
E.value->initialize_library(p_level);
@ -221,6 +237,10 @@ void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel
}
void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
ERR_FAIL_COND(int32_t(p_level) != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
E.value->deinitialize_library(p_level);
@ -259,6 +279,10 @@ void GDExtensionManager::_reload_all_scripts() {
#endif // TOOLS_ENABLED
void GDExtensionManager::load_extensions() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
while (f.is_valid() && !f->eof_reached()) {
String s = f->get_line().strip_edges();
@ -273,6 +297,9 @@ void GDExtensionManager::load_extensions() {
void GDExtensionManager::reload_extensions() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
bool reloaded = false;
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
if (!E.value->is_reloadable()) {

View File

@ -290,10 +290,28 @@ String OS::get_bundle_icon_path() const {
}
// OS specific path for user://
String OS::get_user_data_dir() const {
String OS::get_user_data_dir(const String &p_user_dir) const {
return ".";
}
String OS::get_user_data_dir() const {
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
if (!appname.is_empty()) {
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
if (use_custom_dir) {
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
return get_user_data_dir(custom_dir);
} else {
return get_user_data_dir(get_godot_dir_name().path_join("app_userdata").path_join(appname));
}
} else {
return get_user_data_dir(get_godot_dir_name().path_join("app_userdata").path_join("[unnamed project]"));
}
}
// Absolute path to res://
String OS::get_resource_dir() const {
return ProjectSettings::get_singleton()->get_resource_path();
@ -304,6 +322,23 @@ String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return ".";
}
void OS::create_lock_file() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
String lock_file_path = get_user_data_dir().path_join(".recovery_mode_lock");
Ref<FileAccess> lock_file = FileAccess::open(lock_file_path, FileAccess::WRITE);
if (lock_file.is_valid()) {
lock_file->close();
}
}
void OS::remove_lock_file() {
String lock_file_path = get_user_data_dir().path_join(".recovery_mode_lock");
DirAccess::remove_absolute(lock_file_path);
}
Error OS::shell_open(const String &p_uri) {
return ERR_UNAVAILABLE;
}

View File

@ -285,6 +285,7 @@ public:
virtual String get_bundle_resource_dir() const;
virtual String get_bundle_icon_path() const;
virtual String get_user_data_dir(const String &p_user_dir) const;
virtual String get_user_data_dir() const;
virtual String get_resource_dir() const;
@ -303,6 +304,9 @@ public:
virtual Error move_to_trash(const String &p_path) { return FAILED; }
void create_lock_file();
void remove_lock_file();
virtual int get_exit_code() const;
// `set_exit_code` should only be used from `SceneTree` (or from a similar
// level, e.g. from the `Main::start` if leaving without creating a `SceneTree`).

View File

@ -959,22 +959,8 @@ void OS_Unix::unset_environment(const String &p_var) const {
unsetenv(p_var.utf8().get_data());
}
String OS_Unix::get_user_data_dir() const {
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
if (!appname.is_empty()) {
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
if (use_custom_dir) {
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
return get_data_path().path_join(custom_dir);
} else {
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname);
}
}
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
String OS_Unix::get_user_data_dir(const String &p_user_dir) const {
return get_data_path().path_join(p_user_dir);
}
String OS_Unix::get_executable_path() const {

View File

@ -106,7 +106,7 @@ public:
virtual void initialize_debugging() override;
virtual String get_executable_path() const override;
virtual String get_user_data_dir() const override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
};
class UnixTerminalLogger : public StdLogger {

View File

@ -91,6 +91,10 @@ EditorDebuggerNode::EditorDebuggerNode() {
remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval");
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
EditorRunBar::get_singleton()->get_pause_button()->connect(SceneStringName(pressed), callable_mp(this, &EditorDebuggerNode::_paused));
}
@ -263,6 +267,10 @@ void EditorDebuggerNode::set_keep_open(bool p_keep_open) {
}
Error EditorDebuggerNode::start(const String &p_uri) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return ERR_UNAVAILABLE;
}
ERR_FAIL_COND_V(!p_uri.contains("://"), ERR_INVALID_PARAMETER);
if (keep_open && current_uri == p_uri && server.is_valid()) {
return OK;

View File

@ -730,6 +730,10 @@ void EditorNode::_notification(int p_what) {
CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT);
update_preview_themes(theme_preview_mode);
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Editor functionality has been restricted."), EditorToaster::SEVERITY_WARNING);
}
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
} break;
@ -1152,9 +1156,15 @@ void EditorNode::_sources_changed(bool p_exist) {
if (!singleton->cmdline_export_mode) {
EditorResourcePreview::get_singleton()->start();
}
get_tree()->create_timer(1.0f)->connect("timeout", callable_mp(this, &EditorNode::_remove_lock_file));
}
}
void EditorNode::_remove_lock_file() {
OS::get_singleton()->remove_lock_file();
}
void EditorNode::_scan_external_changes() {
disk_changed_list->clear();
TreeItem *r = disk_changed_list->create_item();
@ -5382,6 +5392,10 @@ void EditorNode::_save_window_settings_to_config(Ref<ConfigFile> p_layout, const
}
void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
return;
}
@ -6613,7 +6627,9 @@ void EditorNode::_feature_profile_changed() {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
}
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
}
@ -6624,7 +6640,9 @@ void EditorNode::_feature_profile_changed() {
editor_dock_manager->set_dock_enabled(history_dock, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
}
if (AssetLibraryEditorPlugin::is_available()) {
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
}
@ -7761,7 +7779,10 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(CanvasItemEditorPlugin));
add_editor_plugin(memnew(Node3DEditorPlugin));
add_editor_plugin(memnew(ScriptEditorPlugin));
add_editor_plugin(memnew(GameViewPlugin));
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
add_editor_plugin(memnew(GameViewPlugin));
}
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();

View File

@ -132,20 +132,6 @@ public:
ACTION_ON_STOP_CLOSE_BUTTOM_PANEL,
};
struct ExecuteThreadArgs {
String path;
List<String> args;
String output;
Thread execute_output_thread;
Mutex execute_output_mutex;
int exitcode = 0;
SafeFlag done;
};
private:
friend class EditorSceneTabs;
friend class SurfaceUpgradeTool;
enum MenuOptions {
FILE_NEW_SCENE,
FILE_NEW_INHERITED_SCENE,
@ -235,6 +221,20 @@ private:
TOOL_MENU_BASE = 1000
};
struct ExecuteThreadArgs {
String path;
List<String> args;
String output;
Thread execute_output_thread;
Mutex execute_output_mutex;
int exitcode = 0;
SafeFlag done;
};
private:
friend class EditorSceneTabs;
friend class SurfaceUpgradeTool;
enum {
MAX_INIT_CALLBACKS = 128,
MAX_BUILD_CALLBACKS = 128
@ -548,6 +548,7 @@ private:
void _resources_reimporting(const Vector<String> &p_resources);
void _resources_reimported(const Vector<String> &p_resources);
void _sources_changed(bool p_exist);
void _remove_lock_file();
void _node_renamed();
void _save_editor_states(const String &p_file, int p_idx = -1);

View File

@ -40,6 +40,8 @@
#include "editor/editor_string_names.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/gui/editor_toaster.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
@ -52,7 +54,35 @@ void EditorRunBar::_notification(int p_what) {
_reset_play_buttons();
} break;
case NOTIFICATION_READY: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_show_dialog();
}
} break;
case NOTIFICATION_THEME_CHANGED: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
main_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("LaunchPadRecoveryMode"), EditorStringName(EditorStyles)));
recovery_mode_panel->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
recovery_mode_button->add_theme_style_override("hover", get_theme_stylebox(SNAME("RecoveryModeButton"), EditorStringName(EditorStyles)));
recovery_mode_button->set_button_icon(get_editor_theme_icon(SNAME("NodeWarning")));
recovery_mode_reload_button->set_button_icon(get_editor_theme_icon(SNAME("Reload")));
recovery_mode_button->begin_bulk_theme_override();
recovery_mode_button->add_theme_color_override("icon_normal_color", Color(0.3, 0.3, 0.3, 1));
recovery_mode_button->add_theme_color_override("icon_pressed_color", Color(0.4, 0.4, 0.4, 1));
recovery_mode_button->add_theme_color_override("icon_hover_color", Color(0.6, 0.6, 0.6, 1));
Color dark_color = get_theme_color("recovery_mode_text_color", EditorStringName(Editor));
recovery_mode_button->add_theme_color_override(SceneStringName(font_color), dark_color);
recovery_mode_button->add_theme_color_override("font_pressed_color", dark_color.lightened(0.2));
recovery_mode_button->add_theme_color_override("font_hover_color", dark_color.lightened(0.4));
recovery_mode_button->add_theme_color_override("font_hover_pressed_color", dark_color.lightened(0.2));
recovery_mode_button->end_bulk_theme_override();
return;
}
_update_play_buttons();
profiler_autostart_indicator->set_button_icon(get_editor_theme_icon(SNAME("ProfilerAutostartWarning")));
pause_button->set_button_icon(get_editor_theme_icon(SNAME("Pause")));
@ -79,6 +109,10 @@ void EditorRunBar::_notification(int p_what) {
}
void EditorRunBar::_reset_play_buttons() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
play_button->set_pressed(false);
play_button->set_button_icon(get_editor_theme_icon(SNAME("MainPlay")));
play_button->set_tooltip_text(TTR("Play the project."));
@ -93,6 +127,10 @@ void EditorRunBar::_reset_play_buttons() {
}
void EditorRunBar::_update_play_buttons() {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
return;
}
_reset_play_buttons();
if (!is_playing()) {
return;
@ -278,7 +316,20 @@ void EditorRunBar::_profiler_autostart_indicator_pressed() {
}
}
void EditorRunBar::recovery_mode_show_dialog() {
recovery_mode_popup->popup_centered();
}
void EditorRunBar::recovery_mode_reload_project() {
EditorNode::get_singleton()->trigger_menu_option(EditorNode::RELOAD_CURRENT_PROJECT, false);
}
void EditorRunBar::play_main_scene(bool p_from_native) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
if (p_from_native) {
run_native->resume_run_native();
} else {
@ -290,6 +341,11 @@ void EditorRunBar::play_main_scene(bool p_from_native) {
}
void EditorRunBar::play_current_scene(bool p_reload) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
String last_current_scene = run_current_filename; // This is necessary to have a copy of the string.
EditorNode::get_singleton()->save_default_environment();
@ -304,6 +360,11 @@ void EditorRunBar::play_current_scene(bool p_reload) {
}
void EditorRunBar::play_custom_scene(const String &p_custom) {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Disable it to run the project."), EditorToaster::SEVERITY_WARNING);
return;
}
stop_playing();
current_mode = RunMode::RUN_CUSTOM;
@ -378,6 +439,7 @@ void EditorRunBar::update_profiler_autostart_indicator() {
bool visual_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_visual_profiler", false);
bool network_profiler_active = EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_network_profiler", false);
bool any_profiler_active = profiler_active | visual_profiler_active | network_profiler_active;
any_profiler_active &= !Engine::get_singleton()->is_recovery_mode_hint();
profiler_autostart_indicator->set_visible(any_profiler_active);
if (any_profiler_active) {
String tooltip = TTR("Autostart is enabled for the following profilers, which can have a performance impact:");
@ -425,6 +487,42 @@ EditorRunBar::EditorRunBar() {
main_hbox = memnew(HBoxContainer);
main_panel->add_child(main_hbox);
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_popup = memnew(AcceptDialog);
recovery_mode_popup->set_min_size(Size2(550, 70) * EDSCALE);
recovery_mode_popup->set_title(TTR("Recovery Mode"));
recovery_mode_popup->set_text(
TTR("Godot opened the project in Recovery Mode, which is a special mode that can help recover projects that crash the engine upon initialization. The following features have been temporarily disabled:") +
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("If the project cannot be opened outside of this mode, then it's very likely any of these components is preventing this project from launching. This mode is intended only for basic editing to troubleshoot such issues, and therefore it is not possible to run a project in this mode.") +
String::utf8("\n\n") + TTR("To disable Recovery Mode, reload the project by pressing the Reload button next to the Recovery Mode banner, or by reopening the project normally."));
recovery_mode_popup->set_autowrap(true);
add_child(recovery_mode_popup);
recovery_mode_reload_button = memnew(Button);
main_hbox->add_child(recovery_mode_reload_button);
recovery_mode_reload_button->set_theme_type_variation("RunBarButton");
recovery_mode_reload_button->set_focus_mode(Control::FOCUS_NONE);
recovery_mode_reload_button->set_tooltip_text(TTR("Disable recovery mode and reload the project."));
recovery_mode_reload_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_reload_project));
recovery_mode_panel = memnew(PanelContainer);
main_hbox->add_child(recovery_mode_panel);
recovery_mode_button = memnew(Button);
recovery_mode_panel->add_child(recovery_mode_button);
recovery_mode_button->set_theme_type_variation("RunBarButton");
recovery_mode_button->set_focus_mode(Control::FOCUS_NONE);
recovery_mode_button->set_text(TTR("Recovery Mode"));
recovery_mode_button->set_tooltip_text(TTR("Recovery Mode is enabled. Click for more details."));
recovery_mode_button->connect(SceneStringName(pressed), callable_mp(this, &EditorRunBar::recovery_mode_show_dialog));
return;
}
play_button = memnew(Button);
main_hbox->add_child(play_button);
play_button->set_theme_type_variation("RunBarButton");

View File

@ -39,6 +39,7 @@ class Button;
class EditorRunNative;
class PanelContainer;
class HBoxContainer;
class AcceptDialog;
class EditorRunBar : public MarginContainer {
GDCLASS(EditorRunBar, MarginContainer);
@ -58,6 +59,11 @@ class EditorRunBar : public MarginContainer {
Button *profiler_autostart_indicator = nullptr;
PanelContainer *recovery_mode_panel = nullptr;
Button *recovery_mode_button = nullptr;
Button *recovery_mode_reload_button = nullptr;
AcceptDialog *recovery_mode_popup = nullptr;
Button *play_button = nullptr;
Button *pause_button = nullptr;
Button *stop_button = nullptr;
@ -95,6 +101,9 @@ protected:
public:
static EditorRunBar *get_singleton() { return singleton; }
void recovery_mode_show_dialog();
void recovery_mode_reload_project();
void play_main_scene(bool p_from_native = false);
void play_current_scene(bool p_reload = false);
void play_custom_scene(const String &p_custom);

View File

@ -570,8 +570,13 @@ void SceneTreeEditor::_update_node(Node *p_node, TreeItem *p_item, bool p_part_o
Color button_color = Color(1, 1, 1);
// Can't set tooltip after adding button, need to do it before.
if (scr->is_tool()) {
additional_notes += "\n" + TTR("This script is currently running in the editor.");
button_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
if (Engine::get_singleton()->is_recovery_mode_hint()) {
additional_notes += "\n" + TTR("This script can run in the editor.\nIt is currently disabled due to recovery mode.");
button_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
} else {
additional_notes += "\n" + TTR("This script is currently running in the editor.");
button_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
}
}
if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == scr) {
additional_notes += "\n" + TTR("This script is a custom type.");

View File

@ -1782,7 +1782,7 @@ bool AssetLibraryEditorPlugin::is_available() {
// directly from GitHub which does not set CORS.
return false;
#else
return StreamPeerTLS::is_available();
return StreamPeerTLS::is_available() && !Engine::get_singleton()->is_recovery_mode_hint();
#endif
}

View File

@ -37,6 +37,9 @@
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
void EditorPluginSettings::_notification(int p_what) {
@ -49,6 +52,12 @@ void EditorPluginSettings::_notification(int p_what) {
plugin_config_dialog->connect("plugin_ready", callable_mp(EditorNode::get_singleton(), &EditorNode::_on_plugin_ready));
plugin_list->connect("button_clicked", callable_mp(this, &EditorPluginSettings::_cell_button_pressed));
} break;
case NOTIFICATION_THEME_CHANGED: {
if (Engine::get_singleton()->is_recovery_mode_hint()) {
recovery_mode_icon->set_texture(get_editor_theme_icon(SNAME("NodeWarning")));
}
} break;
}
}
@ -204,6 +213,23 @@ EditorPluginSettings::EditorPluginSettings() {
plugin_config_dialog->config("");
add_child(plugin_config_dialog);
if (Engine::get_singleton()->is_recovery_mode_hint()) {
HBoxContainer *c = memnew(HBoxContainer);
add_child(c);
recovery_mode_icon = memnew(TextureRect);
recovery_mode_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
c->add_child(recovery_mode_icon);
Label *recovery_mode_label = memnew(Label(TTR("Recovery mode is enabled. Enabled plugins will not run while this mode is active.")));
recovery_mode_label->set_theme_type_variation("HeaderSmall");
recovery_mode_label->set_h_size_flags(SIZE_EXPAND_FILL);
c->add_child(recovery_mode_label);
HSeparator *sep = memnew(HSeparator);
add_child(sep);
}
HBoxContainer *title_hb = memnew(HBoxContainer);
Label *label = memnew(Label(TTR("Installed Plugins:")));
label->set_theme_type_variation("HeaderSmall");

View File

@ -33,6 +33,7 @@
#include "editor/plugins/plugin_config_dialog.h"
class TextureRect;
class Tree;
class EditorPluginSettings : public VBoxContainer {
@ -54,6 +55,7 @@ class EditorPluginSettings : public VBoxContainer {
};
PluginConfigDialog *plugin_config_dialog = nullptr;
TextureRect *recovery_mode_icon = nullptr;
Tree *plugin_list = nullptr;
bool updating = false;

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));

View File

@ -44,6 +44,7 @@ class LineEdit;
class MarginContainer;
class OptionButton;
class PanelContainer;
class PopupMenu;
class ProjectDialog;
class ProjectList;
class QuickSettingsDialog;
@ -145,12 +146,16 @@ class ProjectManager : public Control {
Button *import_btn = nullptr;
Button *scan_btn = nullptr;
Button *open_btn = nullptr;
Button *open_options_btn = nullptr;
Button *run_btn = nullptr;
Button *rename_btn = nullptr;
Button *manage_tags_btn = nullptr;
Button *erase_btn = nullptr;
Button *erase_missing_btn = nullptr;
HBoxContainer *open_btn_container = nullptr;
PopupMenu *open_options_popup = nullptr;
EditorFileDialog *scan_dir = nullptr;
ConfirmationDialog *erase_ask = nullptr;
@ -161,6 +166,7 @@ class ProjectManager : public Control {
ConfirmationDialog *erase_missing_ask = nullptr;
ConfirmationDialog *multi_open_ask = nullptr;
ConfirmationDialog *multi_run_ask = nullptr;
ConfirmationDialog *open_recovery_mode_ask = nullptr;
ProjectDialog *project_dialog = nullptr;
@ -168,8 +174,9 @@ class ProjectManager : public Control {
void _run_project();
void _run_project_confirm();
void _open_selected_projects();
void _open_selected_projects_ask();
void _open_selected_projects_with_migration();
void _open_selected_projects_check_warnings();
void _open_selected_projects_check_recovery_mode();
void _install_project(const String &p_zip_path, const String &p_title);
void _import_project();
@ -180,9 +187,14 @@ class ProjectManager : public Control {
void _erase_project_confirm();
void _erase_missing_projects_confirm();
void _update_project_buttons();
void _open_options_popup();
void _open_recovery_mode_ask(bool manual = false);
void _on_project_created(const String &dir, bool edit);
void _on_projects_updated();
void _on_open_options_selected(int p_option);
void _on_recovery_mode_popup_open_normal();
void _on_recovery_mode_popup_open_recovery();
void _on_order_option_changed(int p_idx);
void _on_search_term_changed(const String &p_term);
@ -218,6 +230,7 @@ class ProjectManager : public Control {
Button *full_convert_button = nullptr;
String version_convert_feature;
bool open_in_recovery_mode = false;
#ifndef DISABLE_DEPRECATED
void _minor_project_migrate();

View File

@ -468,14 +468,16 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
String conf = p_path.path_join("project.godot");
bool grayed = false;
bool missing = false;
bool recovery_mode = false;
Ref<ConfigFile> cf = memnew(ConfigFile);
Error cf_err = cf->load(conf);
int config_version = 0;
String cf_project_name;
String project_name = TTR("Unnamed Project");
if (cf_err == OK) {
String cf_project_name = cf->get_value("application", "config/name", "");
cf_project_name = cf->get_value("application", "config/name", "");
if (!cf_project_name.is_empty()) {
project_name = cf_project_name.xml_unescape();
}
@ -548,7 +550,29 @@ ProjectList::Item ProjectList::load_project_data(const String &p_path, bool p_fa
ProjectManager::get_singleton()->add_new_tag(tag);
}
return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, config_version);
// We can't use OS::get_user_dir() because it attempts to load paths from the current loaded project through ProjectSettings,
// while here we're parsing project files externally. Therefore, we have to replicate its behavior.
String user_dir;
if (!cf_project_name.is_empty()) {
String appname = OS::get_singleton()->get_safe_dir_name(cf_project_name);
bool use_custom_dir = cf->get_value("application", "config/use_custom_user_dir", false);
if (use_custom_dir) {
String custom_dir = OS::get_singleton()->get_safe_dir_name(cf->get_value("application", "config/custom_user_dir_name", ""), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
user_dir = custom_dir;
} else {
user_dir = OS::get_singleton()->get_godot_dir_name().path_join("app_userdata").path_join(appname);
}
} else {
user_dir = OS::get_singleton()->get_godot_dir_name().path_join("app_userdata").path_join("[unnamed project]");
}
String recovery_mode_lock_file = OS::get_singleton()->get_user_data_dir(user_dir).path_join(".recovery_mode_lock");
recovery_mode = FileAccess::exists(recovery_mode_lock_file);
return Item(project_name, description, project_version, tags, p_path, icon, main_scene, unsupported_features, last_edited, p_favorite, grayed, missing, recovery_mode, config_version);
}
void ProjectList::_update_icons_async() {

View File

@ -116,6 +116,7 @@ public:
bool favorite = false;
bool grayed = false;
bool missing = false;
bool recovery_mode = false;
int version = 0;
ProjectListItemControl *control = nullptr;
@ -134,6 +135,7 @@ public:
bool p_favorite,
bool p_grayed,
bool p_missing,
bool p_recovery_mode,
int p_version) {
project_name = p_name;
description = p_description;
@ -147,6 +149,7 @@ public:
favorite = p_favorite;
grayed = p_grayed;
missing = p_missing;
recovery_mode = p_recovery_mode;
version = p_version;
control = nullptr;

View File

@ -214,7 +214,9 @@ void register_editor_types() {
EditorPlugins::add_by_type<ControlEditorPlugin>();
EditorPlugins::add_by_type<CPUParticles3DEditorPlugin>();
EditorPlugins::add_by_type<CurveEditorPlugin>();
EditorPlugins::add_by_type<DebugAdapterServer>();
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
EditorPlugins::add_by_type<DebugAdapterServer>();
}
EditorPlugins::add_by_type<FontEditorPlugin>();
EditorPlugins::add_by_type<GPUParticles3DEditorPlugin>();
EditorPlugins::add_by_type<GPUParticlesCollisionSDF3DEditorPlugin>();

View File

@ -1945,6 +1945,11 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
style_launch_pad_movie->set_border_color(p_config.accent_color);
style_launch_pad_movie->set_border_width_all(Math::round(2 * EDSCALE));
p_theme->set_stylebox("LaunchPadMovieMode", EditorStringName(EditorStyles), style_launch_pad_movie);
Ref<StyleBoxFlat> style_launch_pad_recovery_mode = style_launch_pad->duplicate();
style_launch_pad_recovery_mode->set_bg_color(p_config.accent_color * Color(1, 1, 1, 0.1));
style_launch_pad_recovery_mode->set_border_color(p_config.warning_color);
style_launch_pad_recovery_mode->set_border_width_all(Math::round(2 * EDSCALE));
p_theme->set_stylebox("LaunchPadRecoveryMode", EditorStringName(EditorStyles), style_launch_pad_recovery_mode);
p_theme->set_stylebox("MovieWriterButtonNormal", EditorStringName(EditorStyles), make_empty_stylebox(0, 0, 0, 0));
Ref<StyleBoxFlat> style_write_movie_button = p_config.button_style_pressed->duplicate();
@ -1970,6 +1975,17 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme
p_theme->set_stylebox(CoreStringName(normal), "ProfilerAutostartIndicator", style_profiler_autostart);
p_theme->set_stylebox(SceneStringName(pressed), "ProfilerAutostartIndicator", style_profiler_autostart);
p_theme->set_stylebox("hover", "ProfilerAutostartIndicator", style_profiler_autostart);
// Recovery mode button style
Ref<StyleBoxFlat> style_recovery_mode_button = p_config.button_style_pressed->duplicate();
style_recovery_mode_button->set_bg_color(p_config.warning_color);
style_recovery_mode_button->set_corner_radius_all(p_config.corner_radius * EDSCALE);
style_recovery_mode_button->set_content_margin_all(0);
// Recovery mode button is implicitly styled from the panel's background.
// So, remove any existing borders. (e.g. from draw_extra_borders config)
style_recovery_mode_button->set_border_width_all(0);
style_recovery_mode_button->set_expand_margin(SIDE_RIGHT, 2 * EDSCALE);
p_theme->set_stylebox("RecoveryModeButton", EditorStringName(EditorStyles), style_recovery_mode_button);
}
// Standard GUI variations.

View File

@ -199,6 +199,7 @@ static uint64_t quit_after = 0;
static OS::ProcessID editor_pid = 0;
#ifdef TOOLS_ENABLED
static bool found_project = false;
static bool recovery_mode = false;
static bool auto_build_solutions = false;
static String debug_server_uri;
static bool wait_for_import = false;
@ -551,6 +552,7 @@ void Main::print_help(const char *p_binary) {
#ifdef TOOLS_ENABLED
print_help_option("-e, --editor", "Start the editor instead of running the scene.\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("-p, --project-manager", "Start the project manager, even if a project is auto-detected.\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("--recovery-mode", "Start the editor in recovery mode, which disables features that can typically cause startup crashes, such as tool scripts, editor plugins, GDExtension addons, and others.\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("--debug-server <uri>", "Start the editor debug server (<protocol>://<host/IP>[:port], e.g. tcp://127.0.0.1:6007)\n", CLI_OPTION_AVAILABILITY_EDITOR);
print_help_option("--dap-port <port>", "Use the specified port for the GDScript Debugger Adaptor protocol. Recommended port range [1024, 49151].\n", CLI_OPTION_AVAILABILITY_EDITOR);
#if defined(MODULE_GDSCRIPT_ENABLED) && !defined(GDSCRIPT_NO_LSP)
@ -1435,6 +1437,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
editor = true;
} else if (arg == "-p" || arg == "--project-manager") { // starts project manager
project_manager = true;
} else if (arg == "--recovery-mode") { // Enables recovery mode.
recovery_mode = true;
} else if (arg == "--debug-server") {
if (N) {
debug_server_uri = N->get();
@ -1904,6 +1908,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Engine::get_singleton()->set_editor_hint(true);
Engine::get_singleton()->set_extension_reloading_enabled(true);
// Create initialization lock file to detect crashes during startup.
OS::get_singleton()->create_lock_file();
main_args.push_back("--editor");
if (!init_windowed && !init_fullscreen) {
init_maximized = true;
@ -1919,6 +1926,15 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (project_manager) {
Engine::get_singleton()->set_project_manager_hint(true);
}
if (recovery_mode) {
if (project_manager || !editor) {
OS::get_singleton()->print("Error: Recovery mode can only be used in the editor. Aborting.\n");
goto error;
}
Engine::get_singleton()->set_recovery_mode_hint(true);
}
#endif
OS::get_singleton()->set_cmdline(execpath, main_args, user_args);
@ -2706,6 +2722,10 @@ error:
print_help(execpath);
}
if (editor) {
OS::get_singleton()->remove_lock_file();
}
EngineDebugger::deinitialize();
if (performance) {
@ -3620,6 +3640,8 @@ int Main::start() {
editor = true;
} else if (E->get() == "-p" || E->get() == "--project-manager") {
project_manager = true;
} else if (E->get() == "--recovery-mode") {
recovery_mode = true;
} else if (E->get() == "--install-android-build-template") {
install_android_build_template = true;
#endif // TOOLS_ENABLED
@ -4213,7 +4235,7 @@ int Main::start() {
#ifdef TOOLS_ENABLED
if (editor) {
if (game_path != String(GLOBAL_GET("application/run/main_scene")) || !editor_node->has_scenes_in_session()) {
if (!recovery_mode && (game_path != String(GLOBAL_GET("application/run/main_scene")) || !editor_node->has_scenes_in_session())) {
Error serr = editor_node->load_scene(local_game_path);
if (serr != OK) {
ERR_PRINT("Failed to load scene");
@ -4296,6 +4318,10 @@ int Main::start() {
Crypto::load_default_certificates(
EditorSettings::get_singleton()->get_setting("network/tls/editor_tls_certificates").operator String());
}
if (recovery_mode) {
Engine::get_singleton()->set_recovery_mode_hint(true);
}
#endif
}

View File

@ -254,7 +254,7 @@ Variant GDScript::_new(const Variant **p_args, int p_argcount, Callable::CallErr
bool GDScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
return valid && (tool || ScriptServer::is_scripting_enabled());
return valid && (tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_recovery_mode_hint();
#else
return valid;
#endif

View File

@ -2328,7 +2328,7 @@ void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) {
bool CSharpScript::can_instantiate() const {
#ifdef TOOLS_ENABLED
bool extra_cond = type_info.is_tool || ScriptServer::is_scripting_enabled();
bool extra_cond = (type_info.is_tool || ScriptServer::is_scripting_enabled()) && !Engine::get_singleton()->is_recovery_mode_hint();
#else
bool extra_cond = true;
#endif

View File

@ -118,7 +118,7 @@ String GodotIOJavaWrapper::get_temp_dir() {
}
}
String GodotIOJavaWrapper::get_user_data_dir() {
String GodotIOJavaWrapper::get_user_data_dir(const String &p_user_dir) {
if (_get_data_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, String());

View File

@ -73,7 +73,7 @@ public:
Error open_uri(const String &p_uri);
String get_cache_dir();
String get_temp_dir();
String get_user_data_dir();
String get_user_data_dir(const String &p_user_dir);
String get_locale();
String get_model();
int get_screen_dpi();

View File

@ -413,7 +413,7 @@ String OS_Android::get_model_name() const {
}
String OS_Android::get_data_path() const {
return get_user_data_dir();
return OS::get_user_data_dir();
}
void OS_Android::_load_system_font_config() const {
@ -647,12 +647,12 @@ String OS_Android::get_executable_path() const {
return OS::get_executable_path();
}
String OS_Android::get_user_data_dir() const {
String OS_Android::get_user_data_dir(const String &p_user_dir) const {
if (!data_dir_cache.is_empty()) {
return data_dir_cache;
}
String data_dir = godot_io_java->get_user_data_dir();
String data_dir = godot_io_java->get_user_data_dir(p_user_dir);
if (!data_dir.is_empty()) {
data_dir_cache = _remove_symlink(data_dir);
return data_dir_cache;
@ -764,7 +764,7 @@ void OS_Android::vibrate_handheld(int p_duration_ms, float p_amplitude) {
}
String OS_Android::get_config_path() const {
return get_user_data_dir().path_join("config");
return OS::get_user_data_dir().path_join("config");
}
void OS_Android::benchmark_begin_measure(const String &p_context, const String &p_what) {
@ -897,7 +897,7 @@ String OS_Android::get_system_ca_certificates() {
}
Error OS_Android::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) {
r_project_path = get_user_data_dir();
r_project_path = OS::get_user_data_dir();
Error err = OS_Unix::setup_remote_filesystem(p_server_host, p_port, p_password, r_project_path);
if (err == OK) {
remote_fs_dir = r_project_path;

View File

@ -146,7 +146,7 @@ public:
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
virtual String get_executable_path() const override;
virtual String get_user_data_dir() const override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
virtual String get_data_path() const override;
virtual String get_cache_path() const override;
virtual String get_temp_path() const override;

View File

@ -114,7 +114,7 @@ public:
virtual Error shell_open(const String &p_uri) override;
virtual String get_user_data_dir() const override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
virtual String get_cache_path() const override;
virtual String get_temp_path() const override;

View File

@ -314,7 +314,7 @@ Error OS_IOS::shell_open(const String &p_uri) {
return OK;
}
String OS_IOS::get_user_data_dir() const {
String OS_IOS::get_user_data_dir(const String &p_user_dir) const {
static String ret;
if (ret.is_empty()) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

View File

@ -182,23 +182,9 @@ void OS_Web::vibrate_handheld(int p_duration_ms, float p_amplitude) {
godot_js_input_vibrate_handheld(p_duration_ms);
}
String OS_Web::get_user_data_dir() const {
String OS_Web::get_user_data_dir(const String &p_user_dir) const {
String userfs = "/userfs";
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
if (!appname.is_empty()) {
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
if (use_custom_dir) {
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
return userfs.path_join(custom_dir).replace("\\", "/");
} else {
return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/");
}
}
return userfs.path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
return userfs.path_join(p_user_dir).replace("\\", "/");
}
String OS_Web::get_cache_path() const {

View File

@ -107,7 +107,7 @@ public:
String get_cache_path() const override;
String get_config_path() const override;
String get_data_path() const override;
String get_user_data_dir() const override;
String get_user_data_dir(const String &p_user_dir) const override;
bool is_userfs_persistent() const override;

View File

@ -2214,22 +2214,8 @@ String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const
return path;
}
String OS_Windows::get_user_data_dir() const {
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
if (!appname.is_empty()) {
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
if (use_custom_dir) {
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
if (custom_dir.is_empty()) {
custom_dir = appname;
}
return get_data_path().path_join(custom_dir).replace("\\", "/");
} else {
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname).replace("\\", "/");
}
}
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
String OS_Windows::get_user_data_dir(const String &p_user_dir) const {
return get_data_path().path_join(p_user_dir).replace("\\", "/");
}
String OS_Windows::get_unique_id() const {

View File

@ -231,7 +231,7 @@ public:
virtual String get_godot_dir_name() const override;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const override;
virtual String get_user_data_dir() const override;
virtual String get_user_data_dir(const String &p_user_dir) const override;
virtual String get_unique_id() const override;