Skip to content

Commit

Permalink
Implement a "Safe Mode" for recovering crashing/hanging projects duri…
Browse files Browse the repository at this point in the history
…ng initialization
  • Loading branch information
rsubtil committed Jul 22, 2024
1 parent e25f3c0 commit 97e6f0f
Show file tree
Hide file tree
Showing 34 changed files with 395 additions and 66 deletions.
7 changes: 7 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class Engine {
bool editor_hint = false;
bool project_manager_hint = false;
bool extension_reloading = false;
bool safe_mode_hint = false;

bool _print_header = true;

Expand Down Expand Up @@ -152,6 +153,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_safe_mode_hint(bool p_enabled) { safe_mode_hint = p_enabled; }
_FORCE_INLINE_ bool is_safe_mode_hint() const { return safe_mode_hint; }
#else
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_editor_hint() const { return false; }
Expand All @@ -161,6 +165,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_safe_mode_hint(bool p_enabled) {}
_FORCE_INLINE_ bool is_safe_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 @@ -69,6 +69,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co
}

GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
if (Engine::get_singleton()->is_safe_mode_hint()) {
return LOAD_STATUS_OK;
}

if (gdextension_map.has(p_path)) {
return LOAD_STATUS_ALREADY_LOADED;
}
Expand All @@ -92,6 +96,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_safe_mode_hint()) {
return LOAD_STATUS_OK;
}

if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
Expand Down Expand Up @@ -134,6 +142,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
}

GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
if (Engine::get_singleton()->is_safe_mode_hint()) {
return LOAD_STATUS_OK;
}

if (!gdextension_map.has(p_path)) {
return LOAD_STATUS_NOT_LOADED;
}
Expand Down Expand Up @@ -180,6 +192,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_safe_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 @@ -188,6 +204,10 @@ void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel
}

void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
if (Engine::get_singleton()->is_safe_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 @@ -226,6 +246,10 @@ void GDExtensionManager::_reload_all_scripts() {
#endif // TOOLS_ENABLED

void GDExtensionManager::load_extensions() {
if (Engine::get_singleton()->is_safe_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 @@ -240,6 +264,9 @@ void GDExtensionManager::load_extensions() {

void GDExtensionManager::reload_extensions() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_safe_mode_hint()) {
return;
}
bool reloaded = false;
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
if (!E.value->is_reloadable()) {
Expand Down
23 changes: 22 additions & 1 deletion core/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,14 @@ 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_appname) const {
return ".";
}

String OS::get_user_data_dir() const {
return get_user_data_dir(get_safe_dir_name(GLOBAL_GET("application/config/name")));
}

// Absolute path to res://
String OS::get_resource_dir() const {
return ProjectSettings::get_singleton()->get_resource_path();
Expand All @@ -301,6 +305,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_safe_mode_hint()) {
return;
}

String lock_file_path = get_user_data_dir().path_join(".safe_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(".safe_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 @@ -274,6 +274,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_appname) const;
virtual String get_user_data_dir() const;
virtual String get_resource_dir() const;

Expand All @@ -292,6 +293,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
9 changes: 4 additions & 5 deletions drivers/unix/os_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -872,18 +872,17 @@ 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()) {
String OS_Unix::get_user_data_dir(const String &p_appname) const {
if (!p_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;
custom_dir = p_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(p_appname);
}
}

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 @@ -100,7 +100,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_appname) 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_safe_mode_hint()) {
return;
}

EditorRunBar::get_singleton()->get_pause_button()->connect(SceneStringName(pressed), callable_mp(this, &EditorDebuggerNode::_paused));
}

Expand Down Expand Up @@ -262,6 +266,10 @@ void EditorDebuggerNode::set_keep_open(bool p_keep_open) {
}

Error EditorDebuggerNode::start(const String &p_uri) {
if (Engine::get_singleton()->is_safe_mode_hint()) {
return OK;
}

ERR_FAIL_COND_V(!p_uri.contains("://"), ERR_INVALID_PARAMETER);
if (keep_open && current_uri == p_uri && server.is_valid()) {
return OK;
Expand Down
14 changes: 14 additions & 0 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,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_safe_mode_hint()) {
EditorToaster::get_singleton()->popup_str(TTR("Safe 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 @@ -1116,9 +1120,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 @@ -5249,6 +5259,10 @@ void EditorNode::_load_central_editor_layout_from_config(Ref<ConfigFile> p_confi
}

void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) {
if (Engine::get_singleton()->is_safe_mode_hint()) {
return;
}

if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
return;
}
Expand Down
29 changes: 15 additions & 14 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,20 +151,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 @@ -254,6 +240,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 @@ -559,6 +559,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 _editor_select_next();
Expand Down
Loading

0 comments on commit 97e6f0f

Please sign in to comment.