Skip to content

Commit

Permalink
Implement a "Recovery Mode" for recovering crashing/hanging projects …
Browse files Browse the repository at this point in the history
…during initialization
  • Loading branch information
rsubtil committed Dec 7, 2024
1 parent aa8d9b8 commit 1fce89c
Show file tree
Hide file tree
Showing 34 changed files with 473 additions and 96 deletions.
7 changes: 7 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class Engine {
bool editor_hint = false;
bool project_manager_hint = false;
bool extension_reloading = false;
bool recovery_mode_hint = false;

bool _print_header = true;

Expand Down Expand Up @@ -162,6 +163,9 @@ class Engine {

_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; }
Expand All @@ -171,6 +175,9 @@ class Engine {

_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;
Expand Down
27 changes: 27 additions & 0 deletions core/extension/gdextension_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -215,6 +231,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);
Expand Down Expand Up @@ -253,6 +273,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();
Expand All @@ -267,6 +291,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()) {
Expand Down
37 changes: 36 additions & 1 deletion core/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,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();
Expand All @@ -305,6 +323,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;
}
Expand Down
4 changes: 4 additions & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ class OS {
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;

Expand All @@ -309,6 +310,9 @@ class OS {

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`).
Expand Down
18 changes: 2 additions & 16 deletions drivers/unix/os_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion drivers/unix/os_unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class OS_Unix : public OS {
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 {
Expand Down
8 changes: 8 additions & 0 deletions editor/debugger/editor_debugger_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down Expand Up @@ -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;
Expand Down
27 changes: 24 additions & 3 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,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;

Expand Down Expand Up @@ -1154,9 +1158,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();
Expand Down Expand Up @@ -5368,6 +5378,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;
}
Expand Down Expand Up @@ -6596,7 +6610,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));
}
Expand All @@ -6607,7 +6623,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);
}
Expand Down Expand Up @@ -7740,7 +7758,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();

Expand Down
29 changes: 15 additions & 14 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,20 +132,6 @@ class EditorNode : public Node {
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,
Expand Down Expand Up @@ -235,6 +221,20 @@ class EditorNode : public Node {
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
Expand Down Expand Up @@ -543,6 +543,7 @@ class EditorNode : public Node {
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);
Expand Down
Loading

0 comments on commit 1fce89c

Please sign in to comment.