Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ bevy_ui_widgets = [
"bevy_ui",
]

# Load and save user preferences
# Load and save settings
bevy_settings = ["bevy_internal/bevy_settings"]

# Feathers widget collection.
Expand Down Expand Up @@ -5427,14 +5427,14 @@ category = "UI (User Interface)"
wasm = true

[[example]]
name = "persisting_preferences"
path = "examples/app/persisting_preferences.rs"
name = "settings"
path = "examples/app/settings.rs"
doc-scrape-examples = true
required-features = ["bevy_settings"]

[package.metadata.example.persisting_preferences]
name = "User Preferences"
description = "Demonstrates persistence of user preferences"
[package.metadata.example.settings]
name = "Settings"
description = "Demonstrates persistence of settings"
category = "Application"
wasm = true

Expand All @@ -5446,7 +5446,7 @@ required-features = ["bevy_settings"]

[package.metadata.example.persisting_window_settings]
name = "Save Window Position"
description = "Demonstrates saving window position in preferences"
description = "Demonstrates saving window position settings"
category = "Application"
wasm = false

Expand Down
162 changes: 77 additions & 85 deletions crates/bevy_settings/src/lib.rs

Large diffs are not rendered by default.

46 changes: 23 additions & 23 deletions crates/bevy_settings/src/store_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ use bevy_platform::dirs::preferences_dir;
use bevy_tasks::IoTaskPool;
use std::{fs, path::PathBuf};

/// Persistent storage which uses the local filesystem. Preferences will be located in the
/// OS-specific directory for user preferences.
pub(crate) struct PreferencesStore {
/// Persistent storage which uses the local filesystem. Settings will be located in the
/// OS-specific directory for user settings.
pub(crate) struct SettingsStore {
base_path: Option<PathBuf>,
}

impl PreferencesStore {
/// Construct a new filesystem preferences store.
impl SettingsStore {
/// Construct a new filesystem settings store.
///
/// # Arguments
/// * `app_name` - The name of the application. See [`crate::PreferencesPlugin`] for usage.
/// * `app_name` - The name of the application. See [`crate::SettingsPlugin`] for usage.
pub(crate) fn new(app_name: &str) -> Self {
Self {
base_path: if let Some(base_dir) = preferences_dir() {
let prefs_path = base_dir.join(app_name);
debug!("Preferences path: {:?}", prefs_path);
debug!("Settings path: {:?}", prefs_path);
Some(prefs_path)
} else {
warn!("Could not find user configuration directories");
Expand All @@ -34,24 +34,24 @@ impl PreferencesStore {
/// * `contents` - the contents of the file
pub(crate) fn save(&self, filename: &str, contents: toml::Table) {
if let Some(base_path) = &self.base_path {
// Recursively create the preferences directory if it doesn't exist.
// Recursively create the settings directory if it doesn't exist.
let mut dir_builder = fs::DirBuilder::new();
dir_builder.recursive(true);
if let Err(e) = dir_builder.create(base_path.clone()) {
warn!("Could not create preferences directory: {:?}", e);
warn!("Could not create settings directory: {:?}", e);
return;
}

// Save preferences to temp file
// Save settings to temp file
let temp_path = base_path.join(format!("{filename}.toml.new"));
if let Err(e) = fs::write(&temp_path, contents.to_string()) {
error!("Error saving preferences file: {}", e);
error!("Error saving settings file: {}", e);
}

// Replace old prefs file with new one.
let file_path = base_path.join(format!("{filename}.toml"));
if let Err(e) = fs::rename(&temp_path, file_path) {
warn!("Could not save preferences file: {:?}", e);
warn!("Could not save settings file: {:?}", e);
}
}
}
Expand All @@ -65,24 +65,24 @@ impl PreferencesStore {
if let Some(base_path) = &self.base_path {
IoTaskPool::get().scope(|scope| {
scope.spawn(async {
// Recursively create the preferences directory if it doesn't exist.
// Recursively create the settings directory if it doesn't exist.
let mut dir_builder = fs::DirBuilder::new();
dir_builder.recursive(true);
if let Err(e) = dir_builder.create(base_path.clone()) {
warn!("Could not create preferences directory: {:?}", e);
warn!("Could not create settings directory: {:?}", e);
return;
}

// Save preferences to temp file
// Save settings to temp file
let temp_path = base_path.join(format!("{filename}.toml.new"));
if let Err(e) = fs::write(&temp_path, contents.to_string()) {
error!("Error saving preferences file: {}", e);
error!("Error saving settings file: {}", e);
}

// Replace old prefs file with new one.
let file_path = base_path.join(format!("{filename}.toml"));
if let Err(e) = fs::rename(&temp_path, file_path) {
warn!("Could not save preferences file: {:?}", e);
warn!("Could not save settings file: {:?}", e);
}
});
});
Expand All @@ -93,7 +93,7 @@ impl PreferencesStore {
/// be returned.
///
/// # Arguments
/// * `filename` - The name of the preferences file, without the file extension.
/// * `filename` - The name of the settings file, without the file extension.
pub(crate) fn load(&self, filename: &str) -> Option<toml::Table> {
let Some(base_path) = &self.base_path else {
return None;
Expand All @@ -104,34 +104,34 @@ impl PreferencesStore {
}
}

/// Load a preferences file from disk in TOML format.
/// Load a settings file from disk in TOML format.
pub(crate) fn decode_toml_file(file: &PathBuf) -> Option<toml::Table> {
if file.exists() && file.is_file() {
let prefs_str = match fs::read_to_string(file) {
Ok(prefs_str) => prefs_str,
Err(e) => {
error!("Error reading preferences file: {}", e);
error!("Error reading settings file: {}", e);
return None;
}
};

let table_value = match toml::from_str::<toml::Value>(&prefs_str) {
Ok(table_value) => table_value,
Err(e) => {
error!("Error parsing preferences file: {}", e);
error!("Error parsing settings file: {}", e);
return None;
}
};

match table_value {
toml::Value::Table(table) => Some(table),
_ => {
error!("Preferences file must be a table");
error!("Settings file must be a table");
None
}
}
} else {
// Preferences file does not exist yet.
// Settings file does not exist yet.
None
}
}
8 changes: 4 additions & 4 deletions crates/bevy_settings/src/store_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use bevy_tasks::IoTaskPool;
use web_sys::window;

/// Persistent storage which uses browser local storage.
pub(crate) struct PreferencesStore {
pub(crate) struct SettingsStore {
app_name: String,
}

impl PreferencesStore {
impl SettingsStore {
/// Construct a new preferences store for browser local storage.
///
/// # Arguments
/// * `app_name` - The name of the application. See [`crate::PreferencesPlugin`] for usage.
/// * `app_name` - The name of the application. See [`crate::SettingsPlugin`] for usage.
pub fn new(app_name: &str) -> Self {
Self {
app_name: app_name.to_owned(),
Expand Down Expand Up @@ -79,7 +79,7 @@ impl PreferencesStore {
match table_value {
toml::Value::Table(table) => Some(table),
_ => {
error!("Preferences file must be a table");
error!("Settings file must be a table");
None
}
}
Expand Down
2 changes: 1 addition & 1 deletion docs/cargo_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ This is the complete `bevy` cargo feature list, without "profiles" or "collectio
|bevy_remote|Enable the Bevy Remote Protocol|
|bevy_render|Provides rendering functionality|
|bevy_scene|Provides scene functionality|
|bevy_settings|Load and save user preferences|
|bevy_settings|Load and save settings|
|bevy_shader|Provides shaders usable through asset handles.|
|bevy_solari|Provides raytraced lighting (experimental)|
|bevy_sprite|Provides sprite functionality|
Expand Down
4 changes: 2 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ Example | Description
[Plugin Group](../examples/app/plugin_group.rs) | Demonstrates the creation and registration of a custom plugin group
[Render Recovery](../examples/app/render_recovery.rs) | Demonstrates how bevy can recover from rendering failures.
[Return after Run](../examples/app/return_after_run.rs) | Show how to return to main after the Bevy app has exited
[Save Window Position](../examples/window/persisting_window_settings.rs) | Demonstrates saving window position in preferences
[Save Window Position](../examples/window/persisting_window_settings.rs) | Demonstrates saving window position settings
[Settings](../examples/app/settings.rs) | Demonstrates persistence of settings
[Thread Pool Resources](../examples/app/thread_pool_resources.rs) | Creates and customizes the internal thread pool
[User Preferences](../examples/app/persisting_preferences.rs) | Demonstrates persistence of user preferences
[Without Winit](../examples/app/without_winit.rs) | Create an application without winit (runs single time, no event loop)

### Assets
Expand Down
59 changes: 29 additions & 30 deletions examples/app/persisting_preferences.rs → examples/app/settings.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Demonstrates persistence of user preferences.
//! Demonstrates persistence of settings.
//!
//! A counter is shown in the window. It can be incremented and decremented via input press.
//! Its value persists between app sessions via user preferences.
//! Its value persists between app sessions via settings.
//!
//! On desktop, if you quit the app and then restart it, the counter value should display
//! the most recent value the app had before exiting.
Expand All @@ -12,26 +12,23 @@ use std::time::Duration;
use bevy::{
prelude::*,
settings::{
PreferencesPlugin, ReflectSettingsGroup, SavePreferencesDeferred, SavePreferencesSync,
SettingsGroup,
ReflectSettingsGroup, SaveSettingsDeferred, SaveSettingsSync, SettingsGroup, SettingsPlugin,
},
window::{ExitCondition, WindowCloseRequested},
};

fn main() {
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
// We want to intercept the exit so that we can save prefs.
// We want to intercept the exit so that we can save settings.
exit_condition: ExitCondition::DontExit,
primary_window: Some(Window {
title: "Prefs Counter".into(),
..default()
}),
..default()
}))
.add_plugins(PreferencesPlugin::new(
"org.bevy.examples.persisting_preferences",
))
.add_plugins(SettingsPlugin::new("org.bevy.examples.settings"))
.add_systems(Startup, setup)
.add_systems(Update, (show_count, change_count, on_window_close))
.run();
Expand All @@ -45,17 +42,19 @@ struct Counter {

/// A different settings group which has the name group name as the previous. The two groups will be
/// merged into a single section in the config file.
#[derive(Resource, SettingsGroup, Reflect, Default)]
#[derive(Resource, SettingsGroup, Reflect)]
#[reflect(Resource, SettingsGroup, Default)]
#[settings_group(group = "counter")]
#[expect(
dead_code,
reason = "Example showing additional settings in the same group"
)]
struct OtherSettings {
enabled: bool,
}

impl Default for OtherSettings {
fn default() -> Self {
Self { enabled: true }
}
}

#[derive(Component)]
struct CounterDisplay;

Expand Down Expand Up @@ -91,10 +90,20 @@ fn setup(mut commands: Commands) {
});
}

fn show_count(mut query: Query<&mut Text, With<CounterDisplay>>, counter: Res<Counter>) {
if counter.is_changed() {
fn show_count(
mut query: Query<&mut Text, With<CounterDisplay>>,
counter: Res<Counter>,
other: Res<OtherSettings>,
) {
if other.enabled {
if counter.is_changed() {
for mut text in query.iter_mut() {
text.0 = format!("Count: {}", counter.count);
}
}
} else {
for mut text in query.iter_mut() {
text.0 = format!("Count: {}", counter.count);
text.0 = "Disabled".into();
}
}
}
Expand All @@ -115,24 +124,14 @@ fn change_count(
}

if changed {
commands.queue(SavePreferencesDeferred(Duration::from_secs_f32(0.1)));
commands.queue(SaveSettingsDeferred(Duration::from_secs_f32(0.1)));
}
}

fn on_window_close(mut close: MessageReader<WindowCloseRequested>, mut commands: Commands) {
// Save preferences immediately, then quit.
// Save settings immediately, then quit.
if let Some(_close_event) = close.read().next() {
commands.queue(SavePreferencesSync::IfChanged);
commands.queue(ExitAfterSave);
}
}

struct ExitAfterSave;

impl Command for ExitAfterSave {
type Out = ();

fn apply(self, world: &mut World) {
world.write_message(AppExit::Success);
commands.queue(SaveSettingsSync::IfChanged);
commands.write_message(AppExit::Success);
}
}
Loading
Loading