Skip to content

Commit b77aa47

Browse files
committed
Implement a "Recovery Mode" for recovering crashing/hanging projects during initialization
1 parent bdf625b commit b77aa47

34 files changed

+484
-96
lines changed

core/config/engine.h

+7
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class Engine {
8787
bool project_manager_hint = false;
8888
bool extension_reloading = false;
8989
bool embedded_in_editor = false;
90+
bool recovery_mode_hint = false;
9091

9192
bool _print_header = true;
9293

@@ -162,6 +163,9 @@ class Engine {
162163

163164
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) { extension_reloading = p_enabled; }
164165
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return extension_reloading; }
166+
167+
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) { recovery_mode_hint = p_enabled; }
168+
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return recovery_mode_hint; }
165169
#else
166170
_FORCE_INLINE_ void set_editor_hint(bool p_enabled) {}
167171
_FORCE_INLINE_ bool is_editor_hint() const { return false; }
@@ -171,6 +175,9 @@ class Engine {
171175

172176
_FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) {}
173177
_FORCE_INLINE_ bool is_extension_reloading_enabled() const { return false; }
178+
179+
_FORCE_INLINE_ void set_recovery_mode_hint(bool p_enabled) {}
180+
_FORCE_INLINE_ bool is_recovery_mode_hint() const { return false; }
174181
#endif
175182

176183
Dictionary get_version_info() const;

core/extension/gdextension_manager.cpp

+27
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(co
8484
}
8585

8686
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
87+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
88+
return LOAD_STATUS_FAILED;
89+
}
90+
8791
Ref<GDExtensionLibraryLoader> loader;
8892
loader.instantiate();
8993
return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader);
@@ -119,6 +123,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
119123
#else
120124
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");
121125

126+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
127+
return LOAD_STATUS_FAILED;
128+
}
129+
122130
if (!gdextension_map.has(p_path)) {
123131
return LOAD_STATUS_NOT_LOADED;
124132
}
@@ -161,6 +169,10 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String
161169
}
162170

163171
GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
172+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
173+
return LOAD_STATUS_FAILED;
174+
}
175+
164176
if (!gdextension_map.has(p_path)) {
165177
return LOAD_STATUS_NOT_LOADED;
166178
}
@@ -207,6 +219,10 @@ String GDExtensionManager::class_get_icon_path(const String &p_class) const {
207219
}
208220

209221
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
222+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
223+
return;
224+
}
225+
210226
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
211227
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
212228
E.value->initialize_library(p_level);
@@ -221,6 +237,10 @@ void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel
221237
}
222238

223239
void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
240+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
241+
return;
242+
}
243+
224244
ERR_FAIL_COND(int32_t(p_level) != level);
225245
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
226246
E.value->deinitialize_library(p_level);
@@ -259,6 +279,10 @@ void GDExtensionManager::_reload_all_scripts() {
259279
#endif // TOOLS_ENABLED
260280

261281
void GDExtensionManager::load_extensions() {
282+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
283+
return;
284+
}
285+
262286
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
263287
while (f.is_valid() && !f->eof_reached()) {
264288
String s = f->get_line().strip_edges();
@@ -273,6 +297,9 @@ void GDExtensionManager::load_extensions() {
273297

274298
void GDExtensionManager::reload_extensions() {
275299
#ifdef TOOLS_ENABLED
300+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
301+
return;
302+
}
276303
bool reloaded = false;
277304
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
278305
if (!E.value->is_reloadable()) {

core/os/os.cpp

+36-1
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,28 @@ String OS::get_bundle_icon_path() const {
290290
}
291291

292292
// OS specific path for user://
293-
String OS::get_user_data_dir() const {
293+
String OS::get_user_data_dir(const String &p_user_dir) const {
294294
return ".";
295295
}
296296

297+
String OS::get_user_data_dir() const {
298+
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
299+
if (!appname.is_empty()) {
300+
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
301+
if (use_custom_dir) {
302+
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
303+
if (custom_dir.is_empty()) {
304+
custom_dir = appname;
305+
}
306+
return get_user_data_dir(custom_dir);
307+
} else {
308+
return get_user_data_dir(get_godot_dir_name().path_join("app_userdata").path_join(appname));
309+
}
310+
} else {
311+
return get_user_data_dir(get_godot_dir_name().path_join("app_userdata").path_join("[unnamed project]"));
312+
}
313+
}
314+
297315
// Absolute path to res://
298316
String OS::get_resource_dir() const {
299317
return ProjectSettings::get_singleton()->get_resource_path();
@@ -304,6 +322,23 @@ String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
304322
return ".";
305323
}
306324

325+
void OS::create_lock_file() {
326+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
327+
return;
328+
}
329+
330+
String lock_file_path = get_user_data_dir().path_join(".recovery_mode_lock");
331+
Ref<FileAccess> lock_file = FileAccess::open(lock_file_path, FileAccess::WRITE);
332+
if (lock_file.is_valid()) {
333+
lock_file->close();
334+
}
335+
}
336+
337+
void OS::remove_lock_file() {
338+
String lock_file_path = get_user_data_dir().path_join(".recovery_mode_lock");
339+
DirAccess::remove_absolute(lock_file_path);
340+
}
341+
307342
Error OS::shell_open(const String &p_uri) {
308343
return ERR_UNAVAILABLE;
309344
}

core/os/os.h

+4
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,7 @@ class OS {
285285
virtual String get_bundle_resource_dir() const;
286286
virtual String get_bundle_icon_path() const;
287287

288+
virtual String get_user_data_dir(const String &p_user_dir) const;
288289
virtual String get_user_data_dir() const;
289290
virtual String get_resource_dir() const;
290291

@@ -303,6 +304,9 @@ class OS {
303304

304305
virtual Error move_to_trash(const String &p_path) { return FAILED; }
305306

307+
void create_lock_file();
308+
void remove_lock_file();
309+
306310
virtual int get_exit_code() const;
307311
// `set_exit_code` should only be used from `SceneTree` (or from a similar
308312
// level, e.g. from the `Main::start` if leaving without creating a `SceneTree`).

drivers/unix/os_unix.cpp

+2-16
Original file line numberDiff line numberDiff line change
@@ -959,22 +959,8 @@ void OS_Unix::unset_environment(const String &p_var) const {
959959
unsetenv(p_var.utf8().get_data());
960960
}
961961

962-
String OS_Unix::get_user_data_dir() const {
963-
String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
964-
if (!appname.is_empty()) {
965-
bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
966-
if (use_custom_dir) {
967-
String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
968-
if (custom_dir.is_empty()) {
969-
custom_dir = appname;
970-
}
971-
return get_data_path().path_join(custom_dir);
972-
} else {
973-
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname);
974-
}
975-
}
976-
977-
return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
962+
String OS_Unix::get_user_data_dir(const String &p_user_dir) const {
963+
return get_data_path().path_join(p_user_dir);
978964
}
979965

980966
String OS_Unix::get_executable_path() const {

drivers/unix/os_unix.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class OS_Unix : public OS {
106106
virtual void initialize_debugging() override;
107107

108108
virtual String get_executable_path() const override;
109-
virtual String get_user_data_dir() const override;
109+
virtual String get_user_data_dir(const String &p_user_dir) const override;
110110
};
111111

112112
class UnixTerminalLogger : public StdLogger {

editor/debugger/editor_debugger_node.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ EditorDebuggerNode::EditorDebuggerNode() {
9191
remote_scene_tree_timeout = EDITOR_GET("debugger/remote_scene_tree_refresh_interval");
9292
inspect_edited_object_timeout = EDITOR_GET("debugger/remote_inspect_refresh_interval");
9393

94+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
95+
return;
96+
}
97+
9498
EditorRunBar::get_singleton()->get_pause_button()->connect(SceneStringName(pressed), callable_mp(this, &EditorDebuggerNode::_paused));
9599
}
96100

@@ -263,6 +267,10 @@ void EditorDebuggerNode::set_keep_open(bool p_keep_open) {
263267
}
264268

265269
Error EditorDebuggerNode::start(const String &p_uri) {
270+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
271+
return ERR_UNAVAILABLE;
272+
}
273+
266274
ERR_FAIL_COND_V(!p_uri.contains("://"), ERR_INVALID_PARAMETER);
267275
if (keep_open && current_uri == p_uri && server.is_valid()) {
268276
return OK;

editor/editor_node.cpp

+24-3
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,10 @@ void EditorNode::_notification(int p_what) {
730730
CanvasItemEditor::ThemePreviewMode theme_preview_mode = (CanvasItemEditor::ThemePreviewMode)(int)EditorSettings::get_singleton()->get_project_metadata("2d_editor", "theme_preview", CanvasItemEditor::THEME_PREVIEW_PROJECT);
731731
update_preview_themes(theme_preview_mode);
732732

733+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
734+
EditorToaster::get_singleton()->popup_str(TTR("Recovery Mode is enabled. Editor functionality has been restricted."), EditorToaster::SEVERITY_WARNING);
735+
}
736+
733737
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
734738
} break;
735739

@@ -1152,9 +1156,15 @@ void EditorNode::_sources_changed(bool p_exist) {
11521156
if (!singleton->cmdline_export_mode) {
11531157
EditorResourcePreview::get_singleton()->start();
11541158
}
1159+
1160+
get_tree()->create_timer(1.0f)->connect("timeout", callable_mp(this, &EditorNode::_remove_lock_file));
11551161
}
11561162
}
11571163

1164+
void EditorNode::_remove_lock_file() {
1165+
OS::get_singleton()->remove_lock_file();
1166+
}
1167+
11581168
void EditorNode::_scan_external_changes() {
11591169
disk_changed_list->clear();
11601170
TreeItem *r = disk_changed_list->create_item();
@@ -5382,6 +5392,10 @@ void EditorNode::_save_window_settings_to_config(Ref<ConfigFile> p_layout, const
53825392
}
53835393

53845394
void EditorNode::_load_open_scenes_from_config(Ref<ConfigFile> p_layout) {
5395+
if (Engine::get_singleton()->is_recovery_mode_hint()) {
5396+
return;
5397+
}
5398+
53855399
if (!bool(EDITOR_GET("interface/scene_tabs/restore_scenes_on_load"))) {
53865400
return;
53875401
}
@@ -6613,7 +6627,9 @@ void EditorNode::_feature_profile_changed() {
66136627

66146628
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_3D));
66156629
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_SCRIPT));
6616-
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
6630+
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
6631+
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_GAME));
6632+
}
66176633
if (AssetLibraryEditorPlugin::is_available()) {
66186634
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, !profile->is_feature_disabled(EditorFeatureProfile::FEATURE_ASSET_LIB));
66196635
}
@@ -6624,7 +6640,9 @@ void EditorNode::_feature_profile_changed() {
66246640
editor_dock_manager->set_dock_enabled(history_dock, true);
66256641
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_3D, true);
66266642
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_SCRIPT, true);
6627-
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
6643+
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
6644+
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_GAME, true);
6645+
}
66286646
if (AssetLibraryEditorPlugin::is_available()) {
66296647
editor_main_screen->set_button_enabled(EditorMainScreen::EDITOR_ASSETLIB, true);
66306648
}
@@ -7761,7 +7779,10 @@ EditorNode::EditorNode() {
77617779
add_editor_plugin(memnew(CanvasItemEditorPlugin));
77627780
add_editor_plugin(memnew(Node3DEditorPlugin));
77637781
add_editor_plugin(memnew(ScriptEditorPlugin));
7764-
add_editor_plugin(memnew(GameViewPlugin));
7782+
7783+
if (!Engine::get_singleton()->is_recovery_mode_hint()) {
7784+
add_editor_plugin(memnew(GameViewPlugin));
7785+
}
77657786

77667787
EditorAudioBuses *audio_bus_editor = EditorAudioBuses::register_editor();
77677788

editor/editor_node.h

+15-14
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,6 @@ class EditorNode : public Node {
132132
ACTION_ON_STOP_CLOSE_BUTTOM_PANEL,
133133
};
134134

135-
struct ExecuteThreadArgs {
136-
String path;
137-
List<String> args;
138-
String output;
139-
Thread execute_output_thread;
140-
Mutex execute_output_mutex;
141-
int exitcode = 0;
142-
SafeFlag done;
143-
};
144-
145-
private:
146-
friend class EditorSceneTabs;
147-
friend class SurfaceUpgradeTool;
148-
149135
enum MenuOptions {
150136
FILE_NEW_SCENE,
151137
FILE_NEW_INHERITED_SCENE,
@@ -235,6 +221,20 @@ class EditorNode : public Node {
235221
TOOL_MENU_BASE = 1000
236222
};
237223

224+
struct ExecuteThreadArgs {
225+
String path;
226+
List<String> args;
227+
String output;
228+
Thread execute_output_thread;
229+
Mutex execute_output_mutex;
230+
int exitcode = 0;
231+
SafeFlag done;
232+
};
233+
234+
private:
235+
friend class EditorSceneTabs;
236+
friend class SurfaceUpgradeTool;
237+
238238
enum {
239239
MAX_INIT_CALLBACKS = 128,
240240
MAX_BUILD_CALLBACKS = 128
@@ -548,6 +548,7 @@ class EditorNode : public Node {
548548
void _resources_reimporting(const Vector<String> &p_resources);
549549
void _resources_reimported(const Vector<String> &p_resources);
550550
void _sources_changed(bool p_exist);
551+
void _remove_lock_file();
551552

552553
void _node_renamed();
553554
void _save_editor_states(const String &p_file, int p_idx = -1);

0 commit comments

Comments
 (0)