Merge pull request #110748 from MauriceButler/project-setting-changed-signal
Add ability to get list of Project Settings changed, similar to Editor Settings functionality
This commit is contained in:
@ -283,6 +283,13 @@ String ProjectSettings::globalize_path(const String &p_path) const {
|
|||||||
bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
|
bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
|
||||||
_THREAD_SAFE_METHOD_
|
_THREAD_SAFE_METHOD_
|
||||||
|
|
||||||
|
// Early return if value hasn't changed (unless it's being deleted)
|
||||||
|
if (p_value.get_type() != Variant::NIL) {
|
||||||
|
if (props.has(p_name) && props[p_name].variant == p_value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (p_value.get_type() == Variant::NIL) {
|
if (p_value.get_type() == Variant::NIL) {
|
||||||
props.erase(p_name);
|
props.erase(p_name);
|
||||||
if (p_name.operator String().begins_with("autoload/")) {
|
if (p_name.operator String().begins_with("autoload/")) {
|
||||||
@ -304,7 +311,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_version++;
|
_version++;
|
||||||
_queue_changed();
|
_queue_changed(p_name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +357,7 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_version++;
|
_version++;
|
||||||
_queue_changed();
|
_queue_changed(p_name);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,20 +533,31 @@ void ProjectSettings::_get_property_list(List<PropertyInfo> *p_list) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProjectSettings::_queue_changed() {
|
void ProjectSettings::_queue_changed(const StringName &p_name) {
|
||||||
if (is_changed || !MessageQueue::get_singleton() || MessageQueue::get_singleton()->get_max_buffer_usage() == 0) {
|
changed_settings.insert(p_name);
|
||||||
|
|
||||||
|
if (!MessageQueue::get_singleton() || MessageQueue::get_singleton()->get_max_buffer_usage() == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only queue the deferred call once per frame.
|
||||||
|
if (!is_changed) {
|
||||||
is_changed = true;
|
is_changed = true;
|
||||||
callable_mp(this, &ProjectSettings::_emit_changed).call_deferred();
|
callable_mp(this, &ProjectSettings::_emit_changed).call_deferred();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectSettings::_emit_changed() {
|
void ProjectSettings::_emit_changed() {
|
||||||
if (!is_changed) {
|
if (!is_changed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
is_changed = false;
|
is_changed = false;
|
||||||
|
|
||||||
|
// Emit the general settings_changed signal to indicate changes are complete.
|
||||||
emit_signal("settings_changed");
|
emit_signal("settings_changed");
|
||||||
|
|
||||||
|
// Clear the changed settings after emitting the signal
|
||||||
|
changed_settings.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProjectSettings::load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) {
|
bool ProjectSettings::load_resource_pack(const String &p_pack, bool p_replace_files, int p_offset) {
|
||||||
@ -1352,6 +1370,23 @@ Variant ProjectSettings::get_setting(const String &p_setting, const Variant &p_d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PackedStringArray ProjectSettings::get_changed_settings() const {
|
||||||
|
PackedStringArray arr;
|
||||||
|
for (const StringName &setting : changed_settings) {
|
||||||
|
arr.push_back(setting);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectSettings::check_changed_settings_in_group(const String &p_setting_prefix) const {
|
||||||
|
for (const StringName &setting : changed_settings) {
|
||||||
|
if (String(setting).begins_with(p_setting_prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void ProjectSettings::refresh_global_class_list() {
|
void ProjectSettings::refresh_global_class_list() {
|
||||||
// This is called after mounting a new PCK file to pick up class changes.
|
// This is called after mounting a new PCK file to pick up class changes.
|
||||||
is_global_class_list_loaded = false; // Make sure we read from the freshly mounted PCK.
|
is_global_class_list_loaded = false; // Make sure we read from the freshly mounted PCK.
|
||||||
@ -1548,6 +1583,9 @@ void ProjectSettings::_bind_methods() {
|
|||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);
|
ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);
|
||||||
|
|
||||||
|
// Change tracking methods
|
||||||
|
ClassDB::bind_method(D_METHOD("get_changed_settings"), &ProjectSettings::get_changed_settings);
|
||||||
|
ClassDB::bind_method(D_METHOD("check_changed_settings_in_group", "setting_prefix"), &ProjectSettings::check_changed_settings_in_group);
|
||||||
ADD_SIGNAL(MethodInfo("settings_changed"));
|
ADD_SIGNAL(MethodInfo("settings_changed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,9 @@ class ProjectSettings : public Object {
|
|||||||
// and will always detect the initial project settings as a "change".
|
// and will always detect the initial project settings as a "change".
|
||||||
uint32_t _version = 1;
|
uint32_t _version = 1;
|
||||||
|
|
||||||
|
// Track changed settings for get_changed_settings functionality
|
||||||
|
HashSet<StringName> changed_settings;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
typedef HashMap<String, Variant> CustomMap;
|
typedef HashMap<String, Variant> CustomMap;
|
||||||
static inline const String PROJECT_DATA_DIR_NAME_SUFFIX = "godot";
|
static inline const String PROJECT_DATA_DIR_NAME_SUFFIX = "godot";
|
||||||
@ -119,7 +122,7 @@ protected:
|
|||||||
bool _property_can_revert(const StringName &p_name) const;
|
bool _property_can_revert(const StringName &p_name) const;
|
||||||
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
|
||||||
|
|
||||||
void _queue_changed();
|
void _queue_changed(const StringName &p_name);
|
||||||
void _emit_changed();
|
void _emit_changed();
|
||||||
|
|
||||||
static inline ProjectSettings *singleton = nullptr;
|
static inline ProjectSettings *singleton = nullptr;
|
||||||
@ -210,6 +213,10 @@ public:
|
|||||||
|
|
||||||
bool has_custom_feature(const String &p_feature) const;
|
bool has_custom_feature(const String &p_feature) const;
|
||||||
|
|
||||||
|
// Change tracking methods
|
||||||
|
PackedStringArray get_changed_settings() const;
|
||||||
|
bool check_changed_settings_in_group(const String &p_setting_prefix) const;
|
||||||
|
|
||||||
const HashMap<StringName, AutoloadInfo> &get_autoload_list() const;
|
const HashMap<StringName, AutoloadInfo> &get_autoload_list() const;
|
||||||
void add_autoload(const AutoloadInfo &p_autoload);
|
void add_autoload(const AutoloadInfo &p_autoload);
|
||||||
void remove_autoload(const StringName &p_autoload);
|
void remove_autoload(const StringName &p_autoload);
|
||||||
|
|||||||
@ -54,6 +54,13 @@
|
|||||||
[b]Note:[/b] Setting [code]"usage"[/code] for the property is not supported. Use [method set_as_basic], [method set_restart_if_changed], and [method set_as_internal] to modify usage flags.
|
[b]Note:[/b] Setting [code]"usage"[/code] for the property is not supported. Use [method set_as_basic], [method set_restart_if_changed], and [method set_as_internal] to modify usage flags.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="check_changed_settings_in_group" qualifiers="const">
|
||||||
|
<return type="bool" />
|
||||||
|
<param index="0" name="setting_prefix" type="String" />
|
||||||
|
<description>
|
||||||
|
Checks if any settings with the prefix [param setting_prefix] exist in the set of changed settings. See also [method get_changed_settings].
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="clear">
|
<method name="clear">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="name" type="String" />
|
<param index="0" name="name" type="String" />
|
||||||
@ -61,6 +68,12 @@
|
|||||||
Clears the whole configuration (not recommended, may break things).
|
Clears the whole configuration (not recommended, may break things).
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
|
<method name="get_changed_settings" qualifiers="const">
|
||||||
|
<return type="PackedStringArray" />
|
||||||
|
<description>
|
||||||
|
Gets an array of the settings which have been changed since the last save. Note that internally [code]changed_settings[/code] is cleared after a successful save, so generally the most appropriate place to use this method is when processing [signal settings_changed].
|
||||||
|
</description>
|
||||||
|
</method>
|
||||||
<method name="get_global_class_list">
|
<method name="get_global_class_list">
|
||||||
<return type="Dictionary[]" />
|
<return type="Dictionary[]" />
|
||||||
<description>
|
<description>
|
||||||
|
|||||||
@ -155,4 +155,80 @@ TEST_CASE("[ProjectSettings] localize_path") {
|
|||||||
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
|
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[SceneTree][ProjectSettings] settings_changed signal") {
|
||||||
|
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
|
||||||
|
|
||||||
|
ProjectSettings::get_singleton()->set_setting("test_signal_setting", "test_value");
|
||||||
|
MessageQueue::get_singleton()->flush();
|
||||||
|
|
||||||
|
SIGNAL_CHECK("settings_changed", { {} });
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[ProjectSettings] get_changed_settings basic functionality") {
|
||||||
|
String setting_name = "test_changed_setting";
|
||||||
|
ProjectSettings::get_singleton()->set_setting(setting_name, "test_value");
|
||||||
|
|
||||||
|
PackedStringArray changes = ProjectSettings::get_singleton()->get_changed_settings();
|
||||||
|
CHECK(changes.has(setting_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[ProjectSettings] get_changed_settings multiple settings") {
|
||||||
|
ProjectSettings::get_singleton()->set_setting("test_setting_1", "value1");
|
||||||
|
ProjectSettings::get_singleton()->set_setting("test_setting_2", "value2");
|
||||||
|
ProjectSettings::get_singleton()->set_setting("another_group/setting", "value3");
|
||||||
|
|
||||||
|
PackedStringArray changes = ProjectSettings::get_singleton()->get_changed_settings();
|
||||||
|
CHECK(changes.has("test_setting_1"));
|
||||||
|
CHECK(changes.has("test_setting_2"));
|
||||||
|
CHECK(changes.has("another_group/setting"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[ProjectSettings] check_changed_settings_in_group") {
|
||||||
|
ProjectSettings::get_singleton()->set_setting("group1/setting1", "value1");
|
||||||
|
ProjectSettings::get_singleton()->set_setting("group1/setting2", "value2");
|
||||||
|
ProjectSettings::get_singleton()->set_setting("group2/setting1", "value3");
|
||||||
|
ProjectSettings::get_singleton()->set_setting("other_setting", "value4");
|
||||||
|
|
||||||
|
CHECK(ProjectSettings::get_singleton()->check_changed_settings_in_group("group1/"));
|
||||||
|
CHECK(ProjectSettings::get_singleton()->check_changed_settings_in_group("group2/"));
|
||||||
|
CHECK_FALSE(ProjectSettings::get_singleton()->check_changed_settings_in_group("nonexistent/"));
|
||||||
|
|
||||||
|
CHECK(ProjectSettings::get_singleton()->check_changed_settings_in_group("group1"));
|
||||||
|
CHECK(ProjectSettings::get_singleton()->check_changed_settings_in_group("other_setting"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[SceneTree][ProjectSettings] Changes cleared after settings_changed signal") {
|
||||||
|
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
|
||||||
|
|
||||||
|
ProjectSettings::get_singleton()->set_setting("signal_clear_test", "value");
|
||||||
|
|
||||||
|
PackedStringArray changes_before = ProjectSettings::get_singleton()->get_changed_settings();
|
||||||
|
CHECK(changes_before.has("signal_clear_test"));
|
||||||
|
|
||||||
|
MessageQueue::get_singleton()->flush();
|
||||||
|
|
||||||
|
SIGNAL_CHECK("settings_changed", { {} });
|
||||||
|
|
||||||
|
PackedStringArray changes_after = ProjectSettings::get_singleton()->get_changed_settings();
|
||||||
|
CHECK_FALSE(changes_after.has("signal_clear_test"));
|
||||||
|
|
||||||
|
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("[ProjectSettings] No tracking when setting same value") {
|
||||||
|
String setting_name = "same_value_test";
|
||||||
|
String test_value = "same_value";
|
||||||
|
|
||||||
|
ProjectSettings::get_singleton()->set_setting(setting_name, test_value);
|
||||||
|
int count_before = ProjectSettings::get_singleton()->get_changed_settings().size();
|
||||||
|
|
||||||
|
// Setting the same value should not be tracked due to early return.
|
||||||
|
ProjectSettings::get_singleton()->set_setting(setting_name, test_value);
|
||||||
|
int count_after = ProjectSettings::get_singleton()->get_changed_settings().size();
|
||||||
|
|
||||||
|
CHECK_EQ(count_before, count_after);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace TestProjectSettings
|
} // namespace TestProjectSettings
|
||||||
|
|||||||
Reference in New Issue
Block a user