From f0af29346b7d4befc269fcc7dcb6b54c153d7f2d Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Fri, 1 Oct 2021 11:47:28 +0100 Subject: [PATCH] ProjectSettings add dirty flag and project_settings_changed signal Most frames there will be no change in project settings, and it makes no sense to read settings every frame in case of changes, as a large number of string compares are involved. This PR adds a signal to ProjectSettings that can be subscribed to in order to keep local settings up to date with ProjectSettings. In addition a function `ProjectSettings::has_changes()` is provided for objects outside the signal system (e.g. Rasterizers). --- core/project_settings.cpp | 19 +++++ core/project_settings.h | 35 ++++++++++ doc/classes/ProjectSettings.xml | 7 ++ editor/import_defaults_editor.cpp | 4 +- editor/plugins/spatial_editor_plugin.cpp | 88 +++++++++++++++--------- editor/plugins/spatial_editor_plugin.h | 3 + editor/project_settings_editor.cpp | 10 ++- scene/main/scene_tree.cpp | 2 + 8 files changed, 132 insertions(+), 36 deletions(-) diff --git a/core/project_settings.cpp b/core/project_settings.cpp index 6c6e2dc2f1c5..f4a15d73a10d 100644 --- a/core/project_settings.cpp +++ b/core/project_settings.cpp @@ -165,9 +165,26 @@ String ProjectSettings::globalize_path(const String &p_path) const { return p_path; } +void ProjectSettings::update() { + if (_dirty_this_frame) { + // A signal is sent a single time at the end of the frame when project settings + // are changed. This allows objects to respond. + // Alternatively objects outside the signal system can query ProjectSettings::has_changes() + if (_dirty_this_frame == 2) { + emit_signal("project_settings_changed"); + } + + _dirty_this_frame--; + } +} + bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { _THREAD_SAFE_METHOD_ + // marking the project settings as dirty allows them only to be + // checked when dirty. + _dirty_this_frame = 2; + if (p_value.get_type() == Variant::NIL) { props.erase(p_name); } else { @@ -1022,6 +1039,8 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &ProjectSettings::property_get_revert); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); + + ADD_SIGNAL(MethodInfo("project_settings_changed")); } ProjectSettings::ProjectSettings() { diff --git a/core/project_settings.h b/core/project_settings.h index 722f4ac7a4ad..877186c1ad6a 100644 --- a/core/project_settings.h +++ b/core/project_settings.h @@ -35,10 +35,37 @@ #include "core/os/thread_safe.h" #include "core/set.h" +// Querying ProjectSettings is usually done at startup. +// Additionally, in order to keep track of changes to ProjectSettings, +// instead of Querying all the strings every frame just in case of changes, +// there is a signal "project_settings_changed" which objects can subscribe to. + +// E.g. (from another Godot object) +// // Call your user written object function to Query the project settings once at creation, +// perhaps in an ENTER_TREE notification: +// _project_settings_changed() +// // Then connect your function to the signal so it is called every time something changes in future: +// ProjectSettings::get_singleton()->connect("project_settings_changed", this, "_project_settings_changed"); + +// Where for example your function may take the form: +// void _project_settings_changed() { +// _shadowmap_size = GLOBAL_GET("rendering/quality/shadow_atlas/size"); +// } + +// You may want to also disconnect from the signal in EXIT_TREE notification, if your object may be deleted +// before ProjectSettings: +// ProjectSettings::get_singleton()->disconnect("project_settings_changed", this, "_project_settings_changed"); + +// Additionally, for objects that are not regular Godot objects capable of subscribing to signals (e.g. Rasterizers), +// you can also query the function "has_changes()" each frame, +// and update your local settings whenever this is set. + class ProjectSettings : public Object { GDCLASS(ProjectSettings, Object); _THREAD_SAFE_CLASS_ + int _dirty_this_frame = 2; + public: typedef Map CustomMap; static const String PROJECT_DATA_DIR_NAME_SUFFIX; @@ -168,6 +195,14 @@ class ProjectSettings : public Object { bool has_custom_feature(const String &p_feature) const; + // Either use the signal `project_settings_changed` or query this function. + // N.B. _dirty_this_frame is set initially to 2. + // This is to cope with the situation where a project setting is changed in the iteration AFTER it is read. + // There is therefore the potential for a change to be missed. Persisting the counter + // for two frames avoids this, at the cost of a frame delay. + bool has_changes() const { return _dirty_this_frame == 1; } + void update(); + ProjectSettings(); ~ProjectSettings(); }; diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 3cbaedd5616c..fcf408f553c7 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1569,6 +1569,13 @@ Cell size used for the 2D hash grid that [VisibilityNotifier2D] uses (in pixels). + + + + Objects can use this signal to restrict reading of settings only to situations where a change has been made. + + + diff --git a/editor/import_defaults_editor.cpp b/editor/import_defaults_editor.cpp index a5ff346f7c7e..3ae50343a582 100644 --- a/editor/import_defaults_editor.cpp +++ b/editor/import_defaults_editor.cpp @@ -99,8 +99,8 @@ void ImportDefaultsEditor::_save() { } else { ProjectSettings::get_singleton()->set("importer_defaults/" + settings->importer->get_importer_name(), Variant()); } - - emit_signal("project_settings_changed"); + // Calling ProjectSettings::set() causes the signal "project_settings_changed" to be sent to ProjectSettings. + // ProjectSettingsEditor subscribes to this and can reads the settings updated here. } } diff --git a/editor/plugins/spatial_editor_plugin.cpp b/editor/plugins/spatial_editor_plugin.cpp index 0605b7f3b993..eafb1d0c062b 100644 --- a/editor/plugins/spatial_editor_plugin.cpp +++ b/editor/plugins/spatial_editor_plugin.cpp @@ -2462,6 +2462,49 @@ void SpatialEditorPlugin::edited_scene_changed() { } } +void SpatialEditorViewport::_project_settings_changed() { + if (viewport) { + _project_settings_change_pending = false; + + //update shadow atlas if changed + int shadowmap_size = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/size"); + int atlas_q0 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_0_subdiv"); + int atlas_q1 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_1_subdiv"); + int atlas_q2 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_2_subdiv"); + int atlas_q3 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_3_subdiv"); + + viewport->set_shadow_atlas_size(shadowmap_size); + viewport->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q0)); + viewport->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q1)); + viewport->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q2)); + viewport->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q3)); + + // Update MSAA, FXAA, debanding and HDR if changed. + int msaa_mode = ProjectSettings::get_singleton()->get("rendering/quality/filters/msaa"); + viewport->set_msaa(Viewport::MSAA(msaa_mode)); + + bool use_fxaa = ProjectSettings::get_singleton()->get("rendering/quality/filters/use_fxaa"); + viewport->set_use_fxaa(use_fxaa); + + bool use_debanding = ProjectSettings::get_singleton()->get("rendering/quality/filters/use_debanding"); + viewport->set_use_debanding(use_debanding); + + float sharpen_intensity = ProjectSettings::get_singleton()->get("rendering/quality/filters/sharpen_intensity"); + viewport->set_sharpen_intensity(sharpen_intensity); + + bool hdr = ProjectSettings::get_singleton()->get("rendering/quality/depth/hdr"); + viewport->set_hdr(hdr); + + const bool use_32_bpc_depth = ProjectSettings::get_singleton()->get("rendering/quality/depth/use_32_bpc_depth"); + viewport->set_use_32_bpc_depth(use_32_bpc_depth); + + } else { + // Could not update immediately, set a pending update. + // This may never happen, but is included for safety + _project_settings_change_pending = true; + } +} + void SpatialEditorViewport::_notification(int p_what) { if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { bool visible = is_visible_in_tree(); @@ -2583,19 +2626,9 @@ void SpatialEditorViewport::_notification(int p_what) { } } - //update shadow atlas if changed - - int shadowmap_size = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/size"); - int atlas_q0 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_0_subdiv"); - int atlas_q1 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_1_subdiv"); - int atlas_q2 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_2_subdiv"); - int atlas_q3 = ProjectSettings::get_singleton()->get("rendering/quality/shadow_atlas/quadrant_3_subdiv"); - - viewport->set_shadow_atlas_size(shadowmap_size); - viewport->set_shadow_atlas_quadrant_subdiv(0, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q0)); - viewport->set_shadow_atlas_quadrant_subdiv(1, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q1)); - viewport->set_shadow_atlas_quadrant_subdiv(2, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q2)); - viewport->set_shadow_atlas_quadrant_subdiv(3, Viewport::ShadowAtlasQuadrantSubdiv(atlas_q3)); + if (_project_settings_change_pending) { + _project_settings_changed(); + } bool shrink = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION)); @@ -2603,26 +2636,6 @@ void SpatialEditorViewport::_notification(int p_what) { viewport_container->set_stretch_shrink(shrink ? 2 : 1); } - // Update MSAA, FXAA, debanding and HDR if changed. - - int msaa_mode = ProjectSettings::get_singleton()->get("rendering/quality/filters/msaa"); - viewport->set_msaa(Viewport::MSAA(msaa_mode)); - - bool use_fxaa = ProjectSettings::get_singleton()->get("rendering/quality/filters/use_fxaa"); - viewport->set_use_fxaa(use_fxaa); - - bool use_debanding = ProjectSettings::get_singleton()->get("rendering/quality/filters/use_debanding"); - viewport->set_use_debanding(use_debanding); - - float sharpen_intensity = ProjectSettings::get_singleton()->get("rendering/quality/filters/sharpen_intensity"); - viewport->set_sharpen_intensity(sharpen_intensity); - - const bool hdr = ProjectSettings::get_singleton()->get("rendering/quality/depth/hdr"); - viewport->set_hdr(hdr); - - const bool use_32_bpc_depth = ProjectSettings::get_singleton()->get("rendering/quality/depth/use_32_bpc_depth"); - viewport->set_use_32_bpc_depth(use_32_bpc_depth); - bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); info_label->set_visible(show_info); @@ -2689,10 +2702,17 @@ void SpatialEditorViewport::_notification(int p_what) { surface->connect("focus_entered", this, "_surface_focus_enter"); surface->connect("focus_exited", this, "_surface_focus_exit"); + // Ensure we are up to date with project settings + _project_settings_changed(); + + // Any further changes to project settings get a signal + ProjectSettings::get_singleton()->connect("project_settings_changed", this, "_project_settings_changed"); + _init_gizmo_instance(index); } if (p_what == NOTIFICATION_EXIT_TREE) { + ProjectSettings::get_singleton()->disconnect("project_settings_changed", this, "_project_settings_changed"); _finish_gizmo_instances(); } @@ -3582,6 +3602,7 @@ void SpatialEditorViewport::_bind_methods() { ClassDB::bind_method(D_METHOD("_selection_menu_hide"), &SpatialEditorViewport::_selection_menu_hide); ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SpatialEditorViewport::can_drop_data_fw); ClassDB::bind_method(D_METHOD("drop_data_fw"), &SpatialEditorViewport::drop_data_fw); + ClassDB::bind_method(D_METHOD("_project_settings_changed"), &SpatialEditorViewport::_project_settings_changed); ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport"))); ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport"))); @@ -4104,6 +4125,7 @@ SpatialEditorViewport::SpatialEditorViewport(SpatialEditor *p_spatial_editor, Ed gizmo_scale = 1.0; preview_node = nullptr; + _project_settings_change_pending = false; info_label = memnew(Label); info_label->set_anchor_and_margin(MARGIN_LEFT, ANCHOR_END, -90 * EDSCALE); diff --git a/editor/plugins/spatial_editor_plugin.h b/editor/plugins/spatial_editor_plugin.h index f1728338c268..4f0e7322da53 100644 --- a/editor/plugins/spatial_editor_plugin.h +++ b/editor/plugins/spatial_editor_plugin.h @@ -243,6 +243,7 @@ class SpatialEditorViewport : public Control { private: int index; + bool _project_settings_change_pending; ViewType view_type; void _menu_option(int p_option); void _set_auto_orthogonal(); @@ -458,6 +459,8 @@ class SpatialEditorViewport : public Control { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void _project_settings_changed(); + protected: void _notification(int p_what); static void _bind_methods(); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 85ffa50b852d..fe7a1837d85c 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -143,7 +143,16 @@ void ProjectSettingsEditor::_notification(int p_what) { restart_icon->set_texture(get_icon("StatusWarning", "EditorIcons")); restart_label->add_color_override("font_color", get_color("warning_color", "Editor")); + // The ImportDefaultsEditor changes settings which must be read by this object when changed + ProjectSettings::get_singleton()->connect("project_settings_changed", this, "_settings_changed"); + + } break; + case NOTIFICATION_EXIT_TREE: { + if (ProjectSettings::get_singleton()) { + ProjectSettings::get_singleton()->disconnect("project_settings_changed", this, "_settings_changed"); + } } break; + case NOTIFICATION_POPUP_HIDE: { EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "project_settings", get_rect()); set_process_unhandled_input(false); @@ -2141,7 +2150,6 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { import_defaults_editor = memnew(ImportDefaultsEditor); import_defaults_editor->set_name(TTR("Import Defaults")); tab_container->add_child(import_defaults_editor); - import_defaults_editor->connect("project_settings_changed", this, "_settings_changed"); timer = memnew(Timer); timer->set_wait_time(1.5); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 186696b5e5b6..51504d620b6b 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -589,6 +589,8 @@ bool SceneTree::idle(float p_time) { _call_idle_callbacks(); + ProjectSettings::get_singleton()->update(); + #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint()) {