Skip to content

Commit b2d82e6

Browse files
committed
implement addition signal for individual project setting change
1 parent d7382aa commit b2d82e6

File tree

4 files changed

+277
-2
lines changed

4 files changed

+277
-2
lines changed

core/config/project_settings.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,12 @@ String ProjectSettings::globalize_path(const String &p_path) const {
277277
bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
278278
_THREAD_SAFE_METHOD_
279279

280+
// Capture old value before making changes
281+
Variant old_value;
282+
if (props.has(p_name)) {
283+
old_value = props[p_name].variant;
284+
}
285+
280286
if (p_value.get_type() == Variant::NIL) {
281287
props.erase(p_name);
282288
if (p_name.operator String().begins_with("autoload/")) {
@@ -298,7 +304,10 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
298304
}
299305

300306
_version++;
301-
_queue_changed();
307+
308+
if (old_value != p_value) {
309+
_queue_changed(p_name, old_value, p_value);
310+
}
302311
return true;
303312
}
304313

@@ -344,7 +353,10 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
344353
}
345354

346355
_version++;
347-
_queue_changed();
356+
357+
if (old_value != p_value) {
358+
_queue_changed(p_name, old_value, p_value);
359+
}
348360
return true;
349361
}
350362

@@ -528,11 +540,38 @@ void ProjectSettings::_queue_changed() {
528540
callable_mp(this, &ProjectSettings::_emit_changed).call_deferred();
529541
}
530542

543+
void ProjectSettings::_queue_changed(const StringName &p_name, const Variant &p_old_value, const Variant &p_new_value) {
544+
if (!MessageQueue::get_singleton() || MessageQueue::get_singleton()->get_max_buffer_usage() == 0) {
545+
return;
546+
}
547+
548+
// Add this signal to the pending list
549+
PendingSignal signal;
550+
signal.name = p_name;
551+
signal.old_value = p_old_value;
552+
signal.new_value = p_new_value;
553+
pending_signals.push_back(signal);
554+
555+
// Only queue the deferred call once per frame
556+
if (!is_changed) {
557+
is_changed = true;
558+
callable_mp(this, &ProjectSettings::_emit_changed).call_deferred();
559+
}
560+
}
561+
531562
void ProjectSettings::_emit_changed() {
532563
if (!is_changed) {
533564
return;
534565
}
535566
is_changed = false;
567+
568+
// Emit all pending setting_changed signals first.
569+
for (const PendingSignal &signal : pending_signals) {
570+
emit_signal("setting_changed", signal.name, signal.old_value, signal.new_value);
571+
}
572+
pending_signals.clear();
573+
574+
// Then emit the general settings_changed signal to indicate all changes are complete.
536575
emit_signal("settings_changed");
537576
}
538577

@@ -1510,6 +1549,7 @@ void ProjectSettings::_bind_methods() {
15101549
ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd);
15111550

15121551
ADD_SIGNAL(MethodInfo("settings_changed"));
1552+
ADD_SIGNAL(MethodInfo("setting_changed", PropertyInfo(Variant::STRING_NAME, "name"), PropertyInfo(Variant::NIL, "old_value"), PropertyInfo(Variant::NIL, "new_value")));
15131553
}
15141554

15151555
void ProjectSettings::_add_builtin_input_map() {

core/config/project_settings.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ class ProjectSettings : public Object {
4646
// and will always detect the initial project settings as a "change".
4747
uint32_t _version = 1;
4848

49+
// Pending signal data for the new setting_changed signal
50+
struct PendingSignal {
51+
StringName name;
52+
Variant old_value;
53+
Variant new_value;
54+
};
55+
Vector<PendingSignal> pending_signals;
56+
4957
public:
5058
typedef HashMap<String, Variant> CustomMap;
5159
static inline const String PROJECT_DATA_DIR_NAME_SUFFIX = "godot";
@@ -119,6 +127,7 @@ class ProjectSettings : public Object {
119127
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
120128

121129
void _queue_changed();
130+
void _queue_changed(const StringName &p_name, const Variant &p_old_value, const Variant &p_new_value);
122131
void _emit_changed();
123132

124133
static inline ProjectSettings *singleton = nullptr;

doc/classes/ProjectSettings.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3478,6 +3478,14 @@
34783478
</member>
34793479
</members>
34803480
<signals>
3481+
<signal name="setting_changed">
3482+
<param index="0" name="name" type="StringName" />
3483+
<param index="1" name="old_value" type="Variant" />
3484+
<param index="2" name="new_value" type="Variant" />
3485+
<description>
3486+
Emitted when an individual project setting is changed. Provides the setting name, old value, and new value.
3487+
</description>
3488+
</signal>
34813489
<signal name="settings_changed">
34823490
<description>
34833491
Emitted when any setting is changed, up to once per process frame.

tests/core/config/test_project_settings.h

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,222 @@ TEST_CASE("[ProjectSettings] localize_path") {
155155
TestProjectSettingsInternalsAccessor::resource_path() = old_resource_path;
156156
}
157157

158+
TEST_CASE("[SceneTree][ProjectSettings] settings_changed signal") {
159+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
160+
161+
ProjectSettings::get_singleton()->set_setting("test_signal_setting", "test_value");
162+
MessageQueue::get_singleton()->flush();
163+
164+
SIGNAL_CHECK("settings_changed", { {} });
165+
166+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
167+
}
168+
169+
TEST_CASE("[SceneTree][ProjectSettings] setting_changed signal for new setting") {
170+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
171+
172+
String setting_name = "test_new_setting";
173+
String new_value = "new_value";
174+
175+
CHECK_FALSE(ProjectSettings::get_singleton()->has_setting(setting_name));
176+
177+
SIGNAL_DISCARD("setting_changed");
178+
179+
ProjectSettings::get_singleton()->set_setting(setting_name, new_value);
180+
MessageQueue::get_singleton()->flush();
181+
182+
Array expected_args;
183+
expected_args.push_back(StringName(setting_name));
184+
expected_args.push_back(Variant());
185+
expected_args.push_back(new_value);
186+
Array signal_args;
187+
signal_args.push_back(expected_args);
188+
SIGNAL_CHECK("setting_changed", signal_args);
189+
190+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
191+
}
192+
193+
TEST_CASE("[SceneTree][ProjectSettings] setting_changed signal with parameters") {
194+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
195+
196+
String setting_name = "test_old_value_signal";
197+
String old_value = "old_value";
198+
String new_value = "new_value";
199+
200+
ProjectSettings::get_singleton()->set_setting(setting_name, old_value);
201+
MessageQueue::get_singleton()->flush();
202+
203+
SIGNAL_DISCARD("setting_changed");
204+
205+
ProjectSettings::get_singleton()->set_setting(setting_name, new_value);
206+
MessageQueue::get_singleton()->flush();
207+
208+
Array expected_args;
209+
expected_args.push_back(StringName(setting_name));
210+
expected_args.push_back(old_value);
211+
expected_args.push_back(new_value);
212+
Array signal_args;
213+
signal_args.push_back(expected_args);
214+
SIGNAL_CHECK("setting_changed", signal_args);
215+
216+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
217+
}
218+
219+
TEST_CASE("[SceneTree][ProjectSettings] setting_changed signal for setting removal") {
220+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
221+
222+
String setting_name = "test_removal_setting";
223+
String initial_value = "initial_value";
224+
225+
ProjectSettings::get_singleton()->set_setting(setting_name, initial_value);
226+
MessageQueue::get_singleton()->flush();
227+
SIGNAL_DISCARD("setting_changed");
228+
229+
ProjectSettings::get_singleton()->set_setting(setting_name, Variant());
230+
MessageQueue::get_singleton()->flush();
231+
232+
Array expected_args;
233+
expected_args.push_back(StringName(setting_name));
234+
expected_args.push_back(initial_value);
235+
expected_args.push_back(Variant());
236+
Array signal_args;
237+
signal_args.push_back(expected_args);
238+
SIGNAL_CHECK("setting_changed", signal_args);
239+
240+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
241+
}
242+
243+
TEST_CASE("[SceneTree][ProjectSettings] Both signals emitted together") {
244+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
245+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
246+
247+
String setting_name = "test_both_signals";
248+
String old_value = "old_both";
249+
String new_value = "new_both";
250+
251+
ProjectSettings::get_singleton()->set_setting(setting_name, old_value);
252+
MessageQueue::get_singleton()->flush();
253+
SIGNAL_DISCARD("settings_changed");
254+
SIGNAL_DISCARD("setting_changed");
255+
256+
ProjectSettings::get_singleton()->set_setting(setting_name, new_value);
257+
MessageQueue::get_singleton()->flush();
258+
259+
SIGNAL_CHECK("settings_changed", { {} });
260+
261+
Array expected_args;
262+
expected_args.push_back(StringName(setting_name));
263+
expected_args.push_back(old_value);
264+
expected_args.push_back(new_value);
265+
Array signal_args;
266+
signal_args.push_back(expected_args);
267+
SIGNAL_CHECK("setting_changed", signal_args);
268+
269+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
270+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
271+
}
272+
273+
TEST_CASE("[SceneTree][ProjectSettings] No signals when setting same value") {
274+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
275+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
276+
277+
String setting_name = "test_same_value";
278+
String test_value = "same_value";
279+
280+
ProjectSettings::get_singleton()->set_setting(setting_name, test_value);
281+
MessageQueue::get_singleton()->flush();
282+
SIGNAL_DISCARD("settings_changed");
283+
SIGNAL_DISCARD("setting_changed");
284+
285+
// Set the same value again. This should not trigger any signals.
286+
ProjectSettings::get_singleton()->set_setting(setting_name, test_value);
287+
MessageQueue::get_singleton()->flush();
288+
289+
SIGNAL_CHECK_FALSE("settings_changed");
290+
SIGNAL_CHECK_FALSE("setting_changed");
291+
292+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
293+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
294+
}
295+
296+
TEST_CASE("[SceneTree][ProjectSettings] Multiple setting changes in same frame") {
297+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
298+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
299+
300+
SIGNAL_DISCARD("settings_changed");
301+
SIGNAL_DISCARD("setting_changed");
302+
303+
// Change multiple settings in the same frame,
304+
ProjectSettings::get_singleton()->set_setting("setting_1", "value_1");
305+
ProjectSettings::get_singleton()->set_setting("setting_2", "value_2");
306+
307+
MessageQueue::get_singleton()->flush();
308+
309+
Array expected_args1;
310+
expected_args1.push_back(StringName("setting_1"));
311+
expected_args1.push_back(Variant());
312+
expected_args1.push_back("value_1");
313+
314+
Array expected_args2;
315+
expected_args2.push_back(StringName("setting_2"));
316+
expected_args2.push_back(Variant());
317+
expected_args2.push_back("value_2");
318+
319+
Array all_signal_args;
320+
all_signal_args.push_back(expected_args1);
321+
all_signal_args.push_back(expected_args2);
322+
323+
// Should have 2 setting_changed signals.
324+
SIGNAL_CHECK("setting_changed", all_signal_args);
325+
// Should have 1 settings_changed signal.
326+
SIGNAL_CHECK("settings_changed", { {} });
327+
328+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
329+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
330+
}
331+
332+
TEST_CASE("[SceneTree][ProjectSettings] Multiple changes to the same setting only fire for actual changes") {
333+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
334+
SIGNAL_WATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
335+
336+
SIGNAL_DISCARD("settings_changed");
337+
SIGNAL_DISCARD("setting_changed");
338+
339+
// Change setting multiple times in the same frame.
340+
ProjectSettings::get_singleton()->set_setting("reused_setting", "value_1");
341+
ProjectSettings::get_singleton()->set_setting("reused_setting", "value_2");
342+
ProjectSettings::get_singleton()->set_setting("reused_setting", "value_2");
343+
ProjectSettings::get_singleton()->set_setting("reused_setting", "value_3");
344+
345+
MessageQueue::get_singleton()->flush();
346+
347+
Array expected_args1;
348+
expected_args1.push_back(StringName("reused_setting"));
349+
expected_args1.push_back(Variant());
350+
expected_args1.push_back("value_1");
351+
352+
Array expected_args2;
353+
expected_args2.push_back(StringName("reused_setting"));
354+
expected_args2.push_back("value_1");
355+
expected_args2.push_back("value_2");
356+
357+
Array expected_args3;
358+
expected_args3.push_back(StringName("reused_setting"));
359+
expected_args3.push_back("value_2");
360+
expected_args3.push_back("value_3");
361+
362+
Array all_signal_args;
363+
all_signal_args.push_back(expected_args1);
364+
all_signal_args.push_back(expected_args2);
365+
all_signal_args.push_back(expected_args3);
366+
367+
// Should have 3 setting_changed signals as the duplicate value_2 should be ignored.
368+
SIGNAL_CHECK("setting_changed", all_signal_args);
369+
// Should have 1 settings_changed signal.
370+
SIGNAL_CHECK("settings_changed", { {} });
371+
372+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("settings_changed"));
373+
SIGNAL_UNWATCH(ProjectSettings::get_singleton(), SNAME("setting_changed"));
374+
}
375+
158376
} // namespace TestProjectSettings

0 commit comments

Comments
 (0)