Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement preferences system #61

Open
NthTensor opened this issue Oct 3, 2024 · 4 comments
Open

Implement preferences system #61

NthTensor opened this issue Oct 3, 2024 · 4 comments
Labels
S-Needs-Triage This issue needs to be labelled

Comments

@NthTensor
Copy link
Contributor

Users need a way to configure personal (and perhaps project level) settings and preferences.

This is a WIP tracking issue for this bundle of features. Living design-doc is available here: https://hackmd.io/@bevy/editor_prefs.

@NthTensor NthTensor added the S-Needs-Triage This issue needs to be labelled label Oct 3, 2024
@viridia
Copy link

viridia commented Oct 4, 2024

I would add a bit more detail to the "preferences easily definable by plugins" bullet point. Specifically, we want plugins to be able to save data that is important to them, but ultimately it's the app that decides where (on the filesystem) these preferences get written to. Otherwise you'd end up with the problem that two different apps using the same third-party crate might overwrite each other's settings. So it's the app's job to define one or more abstract "buckets" where preferences live, and individual preference users can pick which bucket they want.

The approach I have taken is that preferences are "just resources" - this means you get a lot of things like change detection for free, and plugins can register their own preference types using the standard Bevy APIs. Of course, we don't want to serialize every resource, so an annotation is used to mark which resources are to be saved. Iterating through all resources and serializing the marked ones is relatively straightforward.

Game states are also "just resources", but require a bit of extra plumbing in order to be able to properly handle state transitions.

The simplest approach is to make each resource a separate preference group, but this leads to serialized files that are more complex than they need to be. The reason is because when designing resources in a game, often the way that things are grouped is constrained by technical and performance considerations - for example a property which changes frequently might be placed in it's own resource so that the changes don't effect readers of other properties. So my annotations also provide a way to override the grouping of properties, allowing values that are thematically related to be placed together even if they are in different resources.

@NthTensor
Copy link
Contributor Author

(You should be able to edit that hackMD doc btw).

@viridia
Copy link

viridia commented Oct 6, 2024

A few more comments, based on recent discord discussions.

I honestly think that we shouldn't be building "editor prefs". We should be building "game and editor prefs". Being able to persistently save things like character name, audio volume, graphics settings and so on are a fundamental feature of just about every A-level game. In my opinion, this is absolutely a feature that Bevy should provide out of the box.

I understand the desire to scope things narrowly so as to build the editor as quickly as possible. The argument is that by trying to serve both the needs of the editor and the needs of games, that it will take longer to develop. I don't believe this is true, and in fact I think it's the other way around: the requirements for game preferences are relatively modest and easy to meet, whereas the (to my eyes) increasing complexity of the preferences requirements are all being driven on the editor side.

Nor am I convinced that these additional features (like merging preferences from different sources) are actually necessary. The use cases are hypothetical and speculative. We don't even know, yet, what kinds of settings we will be storing (coming up with a doc of known settings would be a good first step).

A lot of Bevy developers are used to the hierarchical settings of VSCode, but VSCode is a programmer's editor, and is targeted towards people who understand overlays and hierarchies and such. Moreover, VSCode is unusual in that it is meant for (a) large numbers of projects which are (b) created frequently, and which (c) have a huge set of customization options that are different per-project.

Most apps don't do this, and some users would find it confusing to have settings in multiple places. The kinds of users who use the Bevy editor aren't necessarily the same set of people that would use VSCode. This is exactly the kind of argument that the "user stories" for the editor should resolve.

What's the alternative, then? The answer is to do what most apps do: you have some settings which are per-user, and some settings which are per-project. After all, there are some settings - like key bindings - that don't make sense on a per-project basis. Other settings don't make sense per-user. Are there some settings which make sense for both? Maybe, but is that a small number or a large number? We don't know yet.

The way I had envisioned this working is that there would be an additional annotation, such as @PreferencesSet(User) (the default) or @PreferencesSet(Custom("project")) which would be used to mark a resource as belonging to one set or the other. At the app level, we would define mappings from preferences set to specific file locations, so Custom("project") might map to $PROJECT_ROOT/.bevy_editor/project.toml. But each preference property would be loaded and saved to a single location.

In the case where you want to be able to set a "project default", this can be done as a special case: either by saving a blank, empty project (like Blender does), or by bespoke logic in the app itself for just those properties that can be defaulted.

What about the case of multiple instances of the Bevy editor customized for different games? The answer is, how many instances is the typical editor user likely to have? If the answer is one or two, then it's not that much of a burden for the user to set up their per-user preferences manually for each instance of the editor. If it's more than that, then perhaps an explicit "export/import settings" feature might make sense. It also depends on how many per-user preferences we expect to have. If there's less than a dozen fields, it's probably less work for the user to set those fields by hand than to go through a complex export/import process.

Implementation note: per-user preferences are stored in a file whose name is configured by the app at initialization time, in the standard location for preferences on that platform. This means that each variation of the Bevy editor should have a unique name, so that preferences don't get overwritten.

@viridia
Copy link

viridia commented Dec 29, 2024

I think that my earlier ideas on this were too clever; I recently started over with a different and much simpler approach:

https://github.com/viridia/bevy_simple_prefs

My earlier attempt used Bevy reflection to scan for resources that had the preference annotation, and save them. However, this approach has some downsides:

  • Occasionally you need to load a preference setting before App startup. For example, in a typical Bevy app, the window is one of the first things configured, but in order to save the window size and position (so you don't have to drag the window into place every time the app opens), you need to load that settings before the window opens.
  • Not all preferences are stored in resources (although most are).
  • Using reflection to auto-save preferences nudges the user towards storing internal game states in ways that are designed to be easily serializable, which may have unfortunate consequences. For example, an asset inspector widget might want to save the current selected asset so that the user doesn't have to go searching for the asset when they restart the app. Under normal circumstances, the natural way for the asset inspector to represent the current selected asset is with an asset id. However, asset ids cannot be serialized. This tempts the user to instead store the selection internally as a string, which is not as efficient. It would be better to internally represent the selection as an ID, and only convert it to a string when storing or retrieving preferences.

My new approach provides an API for the user to get and set preference groups and properties. It does not use reflection, instead relying on serde to transform Rust data types into TOML or JSON. This means that it is the user's responsibility to update the preference store whenever they change their internal game state. However, this also gives the user the flexibility to represent their game state in a non-serializable format, and then transform it into a serializable format when storing as a preference.

One particularly nice feature of this library is that it supports WASM environments as well as desktop apps. When compiling for WASM, it stores the preferences in LocalStorage instead of using the filesystem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-Needs-Triage This issue needs to be labelled
Projects
None yet
Development

No branches or pull requests

2 participants