bevy-settings requires more validity - #25548
Conversation
`SettingsGroup`s need to be `Resource + Default + Reflect` to actually do anything with the `SettingsPlugin`, so they may as well be trait bounds. Also, since `ReflectSettingsGroup` also needs `ReflectDefault` to function, it may as well panic if that is missing.
mnmaita
left a comment
There was a problem hiding this comment.
I'm really thankful for changes in this crate, as I had some issues while trying to implement it in my projects. Leaving a few comments and suggestions.
| //! | ||
| //! Settings are loaded into resources that implement [`SettingsGroup`](trait@SettingsGroup), | ||
| //! which is best implemented using the derive macro [`SettingsGroup`](derive@SettingsGroup). | ||
| //! In addition, the resource have the `#[reflect(SettingsGroup, Default)]` annotation. |
There was a problem hiding this comment.
| //! In addition, the resource have the `#[reflect(SettingsGroup, Default)]` annotation. | |
| //! In addition, the resource should have the `#[reflect(SettingsGroup, Default)]` annotation. |
There was a problem hiding this comment.
Edited! Thanks for catching that one
There was a problem hiding this comment.
I'd prefer "must have", since otherwise the settings plugin won't load and save that type.
| //! In addition, the resource have the `#[reflect(SettingsGroup, Default)]` annotation. | ||
| //! | ||
| //! Once all these conditions are met, and when [`SettingsPlugin`] is added, systems can query | ||
| //! for settings using like any other resource. |
There was a problem hiding this comment.
| //! for settings using like any other resource. | |
| //! for settings as like any other resource. |
Maybe a native can double check this, but I feel like this reads better.
There was a problem hiding this comment.
Alternatively, "just like"
There was a problem hiding this comment.
I originally meant to do "for settings using Res and ResMut, like any other resource".
| /// ``` | ||
| /// | ||
| /// Note that it's possible to make multiple different types share the same group and file | ||
| /// using this, and that case isn't well tested |
There was a problem hiding this comment.
I'd add tests instead of documenting the lack of them. What would it take to create them?
There was a problem hiding this comment.
Also, it's Bevy house style not to editorialize in user-facing comments (I've tripped up on this myself - it's tempting to justify or hedge, but those discussions belong elsewhere.)
There was a problem hiding this comment.
Honestly I don't think it's been decided what the expected behaviour is if multiple types have the same group or file - or even the same key, which (I checked) is in fact possible.
There was a problem hiding this comment.
Multiple types with the same group and/or file make total sense to me. You could have a "per field" type of configuration where each resource represents a single field in the file. This pattern is useful for change detection for example.
On the other hand, same key for different resources sounds problematic and I don't know how we could prevent that.
There was a problem hiding this comment.
Ideally, same key would produce a warning, but would be expensive to check since you'd have to keep a map of all keys that have been used up to this point. Whether it's worth it is a discussion to have.
Multiple types with the same group is an intentional feature: it's very common in Bevy that we have to split resources apart for technical reasons (change detection, etc.) that have nothing to do with categorization or logical grouping. Forcing each resource to be its own top-level TOML key would make the settings file less legible.
viridia
left a comment
There was a problem hiding this comment.
Looks good generally, just a few nits.
mnmaita
left a comment
There was a problem hiding this comment.
Last minor thing I spotted. Approving anyway, but it should be fixed for correctness in the docs. Thanks for the changes!
| /// | ||
| /// Note that it's possible to make multiple different types share the same group and file | ||
| /// using this, and that case isn't well tested | ||
| /// Note that it's possible to make multiple different settings types share the same file, |
There was a problem hiding this comment.
I think "multiple different setting types" is correct here, instead of "settings types".
| /// When added to an app, `SettingsPlugin` will load settings from storage (either the filesystem | ||
| /// or browser local storage) into resources that implement the [`SettingsGroup`](trait@SettingsGroup), | ||
| /// [`Default`], and [`Reflect`] traits, and, in | ||
| /// addition, are also annotated with `#[reflect(Default, SettingsGroup)]`. The plugin can also be used |
There was a problem hiding this comment.
Nit: if im not mistaken, the plugin isnt used directly to do the save. If you feel the need to mention that the plug in is required in order for save commands to be respected, id be more precise with the wording i.e. "Adding the plugin also enables settings save commands submitted to the commands queue to be respected" or something like that
| // Collect all the errors into a single list so that a user can see all of them at once rather than chasing them | ||
| // down one by one as they fix the errors. | ||
| errors.push(format!( | ||
| "Type {} has #[reflect(SettingsGroup)], which requires #[reflect(Default)]. It will not be saved or loaded.", |
There was a problem hiding this comment.
| "Type {} has #[reflect(SettingsGroup)], which requires #[reflect(Default)]. It will not be saved or loaded.", | |
| "Type {} has #[reflect(SettingsGroup)], which requires #[reflect(Default)] in order for it to be saved or loaded.", |
Imo leaving it as second sentence sounds unrelated / not enough reason for it to be a panic.
Objective
It took me half a day to get bevy-settings working. This is in part due to lacking documentation and in part due to it failing without any errors. In my first PR (#25547), I improved the situation by improving the documentation. However, even with the improved documentation, it's still possible to write a program that compiles and runs without any errors or warnings, but still is multiple lines annotations and trait implementations away from actually letting
SettingsPluginsave and load the settings.Solution
THIS IS A BREAKING CHANGE
This PR adds trait bounds so that types that implement the
SettingsGrouptrait must also implementDefaultandReflect, which are both required forSettingsPluginto save and load the settings. Furthermore,SettingsPluginwill now panic if a type is registered#[reflect(SettingsGroup)]but doesn't register#[reflect(Default)], since only types that are registeredReflectDefaultandReflectSettingsGroupwill be saved and loaded bySettingsPlugin.I justify these breaking changes by how only invalid code (that is, code that tries to use
SettingsPluginandSettingsGroupbut does not manage to letSettingsPluginsave and load all theSettingsGrouptypes) can have compile errors.Testing
SettingsGrouptrait that the trait bounds are getting in the way of?