godot
godot copied to clipboard
`ProjectSettings.set_initial_value()` does not set a setting's default value when queried via `get_setting()`
Godot version
4.0.dev (8f6cba3a4)
System information
Fedora 34, GeForce GTX 1080 (NVIDIA 495.46)
Issue description
ProjectSettings.set_initial_value() does not set a setting's default value when queried via get_setting(). This likely happens because the setting is not persisted to project.godot when its value is equal to the default. One way to address this would be to always save custom settings' values to project.godot, even if they are equal to the default value.
I'm not sure whether this is a bug or design decision. Either way, this should be documented in the class reference. See https://github.com/godotengine/godot-docs/issues/5497#issuecomment-1006771969 where I originally mentioned this.
Steps to reproduce
- Create an editor plugin.
- In the plugin main script's
_enter_tree()function, define a new project setting: https://github.com/Calinou/godot-photo-mode-demo/blob/5029be379192d736d34d270f1c7696f19ec6de29/addons/photo_mode/plugin.gd - In another script, get the project setting's value: https://github.com/Calinou/godot-photo-mode-demo/blob/5029be379192d736d34d270f1c7696f19ec6de29/addons/photo_mode/photo_mode.gd#L238-L244
- The initial value is not returned if the value is not saved to
project.godot. This occurs if the value is not changed from its default. The setting defined here defaults totrue, butnullwill be returned (and converted tofalsewithbool()) if the setting is not persisted toproject.godot.
Minimal reproduction project
https://github.com/Calinou/godot-photo-mode-demo I can make a more minimal reproduction project if needed.
I kind of think it's a bug. with current behavior, custom settings that are equal to default values will be lost after reloading/exporting the project. This makes custom settings less usable in a sense.
The problem is that now we don't have a list of built-in project settings, they are all added in the ProjectSettings constructor, no different from custom settings added manually in the script. Either we save the list of built-in settings somewhere to determine if a setting is a custom project setting or a built-in project setting, or we save all settings, regardless of whether it's the default value or not.🤔
For whatever it's worth, this is also an issue in Godot 3.5.1, especially the part about custom settings that are equal to their default will be lost after reloading/exporting the project.
Any custom settings should be persisted in project.godot regardless. Though, looking through project_settings.cpp, I don't think that there's a way to determine if the setting is custom or something that the editor sets/gets by default.
It is already implied, but i want to point out that has_setting will return false if the setting has the default value.
Which is not what you would expect, especially as the setting is visible in the Godot GUI.
For whatever it's worth, this is also an issue in Godot 3.5.1, especially the part about custom settings that are equal to their default will be lost after reloading/exporting the project.
Holy cow I just spent too long thinking I was going crazy wondering why my custom project settings were disappearing whenever I would call "set_initial_value", but if I omitted that call, they would show up after closing the game and opening project settings again.
I just stumbled upon this too and I would say this is definitely a bug! If I create a project setting - default value or not -, the reasonable assumption is that get_setting returns its value and has_setting returns true.
EDIT: Upon further inspection, it looks like the setting is read correctly in the editor, but not in a running project on current master [d3415ae5a].
Edit 2: After writing the comments below, I might have to backpedal and say that this might not actually be a bug, but just an insufficiently documented feature.
I decided to look more into it and this is what I found.
I created three settings, one empty by default, one overridden in the project settings dialog, and one with a non-empty default value. The settings are retrieved correctly when they're defined in the EditorPlugin and read in the editor. Below is the log output when I try to read them in EditorPlugin._ready():
Editor: trying to read settings defined in the editor plugin
empty:
overridden: Overridden value
default: Default value
When I try to read these same values from a running scene started from the editor with F6, the overridden value is retrieved correctly while the other two can't be found and thus return null:
Runtime: trying to read settings defined in the editor plugin
empty: <null>
overridden: Overridden value
default: <null>
Now, when I create the project settings at runtime in the running project, using the same code as I used in the editor plugin, and try to read the values after that, they're finally retrieved correctly:
Runtime: trying to read settings defined at runtime
empty:
overridden: Overridden value
default: Default value
Since EditorPlugin doesn't run in exported projects, there is no chance for it to create the settings it creates while running in the editor. This isn't a problem for overridden settings because they can be found in the project.godot file. It IS a problem for anything that isn't stored in the project file, like default values that are never overridden.
Here is the MRP I created to test this.
I see several ways to fix this, the two most practical ways being...
- Store all custom settings in the
project.godotfile, regardless of whether they're the defaults or not. This makes the project file more crowded and might involve changing how the project manager detects overridden settings and resets defaults.
- Change the docs to document this pitfall and suggest putting custom settings creation into a separate script that is called from both
EditorPluginand some autoloaded script at the beginning of the game. No changes have to be made to the Godot source. You could argue that some settings are relevant at editing time only and aren't needed at runtime. These can be safely created in theEditorPluginclass only. Anything that needs to be available in the editor and in exported games should be handled like I just described.
I just ran into this issue too, took me some time to find out this was the cause.
I'm wondering how common this is though. I would have been fine personally if this was clearly documented somewhere. I can make a PR for this if this is the way we want to go.
I don't think writing all settings to the project.godot file is a good ideas (as you said, it would make it much more crowded and we might need to touch other areas of the code too).
Then there's still the option to make a system to track which settings are added by users. We could maybe make a new PROPERTY_USAGE_CUSTOM flag which identifies such properties? That way we would have a way to identify which ones need to always be saved.
I was just wondering why when I set ProjectSettings.set_initial_value but don't modify the default value, ProjectSettings.get_setting returns null.
Based on my exploration and testing, I found that the behavior of the Editor and the Game (Runtime) is different when initializing ProjectSettings.
First, we should know, set_initial_value does not set settings. It just mark the fallback default value while setting not set.
Look at the ProjectSettings.cpp ,It uses ProjectSettings.set to create it's settings before set_initial_value too.
Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs, bool p_basic, bool p_internal) {
Variant ret;
if (!ProjectSettings::get_singleton()->has_setting(p_var)) {
ProjectSettings::get_singleton()->set(p_var, p_default);
}
ret = GLOBAL_GET(p_var);
ProjectSettings::get_singleton()->set_initial_value(p_var, p_default);
ProjectSettings::get_singleton()->set_builtin_order(p_var);
ProjectSettings::get_singleton()->set_as_basic(p_var, p_basic);
ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed);
ProjectSettings::get_singleton()->set_ignore_value_in_docs(p_var, p_ignore_value_in_docs);
ProjectSettings::get_singleton()->set_as_internal(p_var, p_internal);
return ret;
}
Variant _GLOBAL_DEF(const PropertyInfo &p_info, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs, bool p_basic, bool p_internal) {
Variant ret = _GLOBAL_DEF(p_info.name, p_default, p_restart_if_changed, p_ignore_value_in_docs, p_basic, p_internal);
ProjectSettings::get_singleton()->set_custom_property_info(p_info);
return ret;
}
Second, ProjectSettings does not save settings which it's value is same to initial. It thought the settings had already been persisted inside it, so it didn't save to proejct.godot. However, in reality, as EditorPlugin does not _enter_tree during game runtime, the custom settings added by EditorPlugin are not defined during game runtime.
This is the confuse thing.
In ProjectSettings.cpp, set_initial_value is actually statically encoded, so as long as ProjectSettings is initialized, the settings defined internally will definitely be correctly initialized. No matter the Editor or the Game, they will both fully initialize ProjectSettings, so there will be no issues with the built-in settings.
However, EditorPlugin is different. EditorPlugin will only _enter_tree in the Editor. It enters the EditorNode.
When running in Game mode, Godot will not execute the _enter_tree of EditorPlugin, because there is no editor.
As Godot developers, especially plugin developers, we tend to register our plugin settings in _enter_tree (this is a consensus and is considered a standard practice) and then clean up our plugin settings in _exit_tree.
@tool
extends EditorPlugin
var setting_name = "test/my_setting"
func _enter_tree():
ProjectSettings.set_setting(setting_name,"hello")
ProjectSettings.add_property_info(
{
name=setting_name,
type=TYPE_STRING,
hint_string=""
}
)
ProjectSettings.set_initial_value(setting_name, "hello")
ProjectSettings.set_as_basic(setting_name, true)
ProjectSettings.save()
func _exit_tree():
ProjectSettings.clear(setting_name)
This results in our _enter_tree of plugin not being executed when Game is running, and naturally, our set_initial_value will not be executed either.
extends Control
func _ready():
printt("test/my_setting", ProjectSettings.get_setting("test/my_setting"))
Got null.
Godot Engine v4.2.1.stable.custom_build.f9cf98f8e - https://godotengine.org
Vulkan API 1.3.260 - Forward+ - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce RTX 4090
test/my_setting <null>
If we do like this.
@tool
extends EditorPlugin
static func register_settings():
var setting_name = "test/my_setting"
ProjectSettings.set_setting(setting_name, "hello")
ProjectSettings.add_property_info(
{
name=setting_name,
type=TYPE_STRING,
hint_string=""
}
)
ProjectSettings.set_initial_value(setting_name, "hello")
ProjectSettings.set_as_basic(setting_name, true)
ProjectSettings.save()
func _enter_tree():
register_settings()
And call register_settings in main scene.
extends Control
func _ready():
preload ("res://addons/my_plugin/plugin.gd").register_settings()
printt("test/my_setting", ProjectSettings.get_setting("test/my_setting"))
Hey, we get it.
Godot Engine v4.2.1.stable.custom_build.f9cf98f8e - https://godotengine.org
Vulkan API 1.3.260 - Forward+ - Using Vulkan Device #0: NVIDIA - NVIDIA GeForce RTX 4090
test/my_setting hello
Summary
Is this a bug?
It's a bit tricky to define because there's no mention in the official documentation that EditorPlugin will handle user-defined settings.
The practice of handling custom settings for Plugins in _enter_tree and _exit_tree is something employed by community developers.
And EditorPlugin is not created when the game is running as there is no editor scene tree, the plugin cannot enter the editor scene tree so it's not created in the first place. This results in _enter_tree ``_exit_tree not being executed during game runtime.
However, I believe that EditorPlugin should handle custom settings, it's reasonable to expect so.
As for the direction of modifications for this BUG, my personal views are:
-
Let EditorPlugin be created even when the game is running. As long as EditorPlugin can execute
_init, then we can do something inside here. The inability to enter the editor scene tree is not a problem. But this may lead to the plugin's settings being added to ProjectSettings before the plugin is activated. -
Or add some static virtual methods for EditorPlugin for users to override (like the example above). This allows the game engine to traverse the plugins to execute these static virtual methods for runtime initialization without instance it.
-
Or just save it into project.godot what ever if it is inital for editorplugin.