From 92f437ec15b1943711af0c5ee446c7ffd2436b21 Mon Sep 17 00:00:00 2001 From: sleepycatcoding <131554884+sleepycatcoding@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:27:50 +0200 Subject: [PATCH] desktop: Add storage backend preference --- core/src/player.rs | 4 +- .../assets/texts/en-US/preferences_dialog.ftl | 4 ++ desktop/src/cli.rs | 9 +++ desktop/src/gui/preferences_dialog.rs | 59 ++++++++++++++++- desktop/src/player.rs | 4 +- desktop/src/preferences.rs | 20 ++++++ desktop/src/preferences/read.rs | 65 ++++++++++++++++++- desktop/src/preferences/storage.rs | 41 ++++++++++++ desktop/src/preferences/write.rs | 25 +++++++ web/src/lib.rs | 2 +- 10 files changed, 226 insertions(+), 7 deletions(-) create mode 100644 desktop/src/preferences/storage.rs diff --git a/core/src/player.rs b/core/src/player.rs index d90c35fae..52c3c2775 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -2268,8 +2268,8 @@ impl PlayerBuilder { /// Sets the storage backend of the player. #[inline] - pub fn with_storage(mut self, storage: impl 'static + StorageBackend) -> Self { - self.storage = Some(Box::new(storage)); + pub fn with_storage(mut self, storage: Box) -> Self { + self.storage = Some(storage); self } diff --git a/desktop/assets/texts/en-US/preferences_dialog.ftl b/desktop/assets/texts/en-US/preferences_dialog.ftl index f6f6f3ff3..4aec62166 100644 --- a/desktop/assets/texts/en-US/preferences_dialog.ftl +++ b/desktop/assets/texts/en-US/preferences_dialog.ftl @@ -17,3 +17,7 @@ audio-output-device-default = System Default log-filename-pattern = Log Filename log-filename-pattern-single-file = Single File (ruffle.log) log-filename-pattern-with-timestamp = With Timestamp + +storage-backend = Save-File Location +storage-backend-disk = Disk +storage-backend-memory = Memory \ No newline at end of file diff --git a/desktop/src/cli.rs b/desktop/src/cli.rs index c7235a606..b613cccc1 100644 --- a/desktop/src/cli.rs +++ b/desktop/src/cli.rs @@ -1,3 +1,4 @@ +use crate::preferences::storage::StorageBackend; use crate::RUFFLE_VERSION; use anyhow::{anyhow, Error}; use clap::{Parser, ValueEnum}; @@ -54,6 +55,12 @@ pub struct Opt { #[clap(long, short)] pub power: Option, + /// Type of storage backend to use. This determines where local storage data is saved (e.g. shared objects). + /// + /// This option temporarily overrides any stored preference. + #[clap(long)] + pub storage: Option, + /// Width of window in pixels. #[clap(long, display_order = 1)] pub width: Option, @@ -101,6 +108,8 @@ pub struct Opt { trace_path: Option, /// Location to store save data for games. + /// + /// This option has no effect if `storage` is not `disk`. #[clap(long, default_value_os_t=get_default_save_directory())] pub save_directory: std::path::PathBuf, diff --git a/desktop/src/gui/preferences_dialog.rs b/desktop/src/gui/preferences_dialog.rs index 7ba9b708d..26933a1a8 100644 --- a/desktop/src/gui/preferences_dialog.rs +++ b/desktop/src/gui/preferences_dialog.rs @@ -1,6 +1,6 @@ use crate::gui::{available_languages, optional_text, text}; use crate::log::FilenamePattern; -use crate::preferences::GlobalPreferences; +use crate::preferences::{storage::StorageBackend, GlobalPreferences}; use cpal::traits::{DeviceTrait, HostTrait}; use egui::{Align2, Button, ComboBox, Grid, Ui, Widget, Window}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; @@ -28,6 +28,10 @@ pub struct PreferencesDialog { log_filename_pattern: FilenamePattern, log_filename_pattern_changed: bool, + + storage_backend: StorageBackend, + storage_backend_readonly: bool, + storage_backend_changed: bool, } impl PreferencesDialog { @@ -64,6 +68,10 @@ impl PreferencesDialog { log_filename_pattern: preferences.log_filename_pattern(), log_filename_pattern_changed: false, + storage_backend: preferences.storage_backend(), + storage_backend_readonly: preferences.cli.storage.is_some(), + storage_backend_changed: false, + preferences, } } @@ -91,6 +99,8 @@ impl PreferencesDialog { self.show_audio_preferences(locale, ui); self.show_log_preferences(locale, ui); + + self.show_storage_preferences(locale, &locked_text, ui); }); if self.restart_required() { @@ -119,6 +129,7 @@ impl PreferencesDialog { || self.power_preference != self.preferences.graphics_power_preference() || self.output_device != self.preferences.output_device_name() || self.log_filename_pattern != self.preferences.log_filename_pattern() + || self.storage_backend != self.preferences.storage_backend() } fn show_graphics_preferences( @@ -266,6 +277,42 @@ impl PreferencesDialog { ui.end_row(); } + fn show_storage_preferences( + &mut self, + locale: &LanguageIdentifier, + locked_text: &str, + ui: &mut Ui, + ) { + ui.label(text(locale, "storage-backend")); + + if self.storage_backend_readonly { + ui.label(storage_backend_name(locale, self.storage_backend)) + .on_hover_text(locked_text); + } else { + let previous = self.storage_backend; + ComboBox::from_id_source("storage-backend") + .selected_text(storage_backend_name(locale, self.storage_backend)) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.storage_backend, + StorageBackend::Disk, + storage_backend_name(locale, StorageBackend::Disk), + ); + ui.selectable_value( + &mut self.storage_backend, + StorageBackend::Memory, + storage_backend_name(locale, StorageBackend::Memory), + ); + }); + + if self.storage_backend != previous { + self.storage_backend_changed = true; + } + } + + ui.end_row(); + } + fn save(&mut self) { if let Err(e) = self.preferences.write_preferences(|preferences| { if self.graphics_backend_changed { @@ -284,6 +331,9 @@ impl PreferencesDialog { if self.log_filename_pattern_changed { preferences.set_log_filename_pattern(self.log_filename_pattern); } + if self.storage_backend_changed { + preferences.set_storage_backend(self.storage_backend); + } }) { // [NA] TODO: Better error handling... everywhere in desktop, really tracing::error!("Could not save preferences: {e}"); @@ -321,6 +371,13 @@ fn filename_pattern_name(locale: &LanguageIdentifier, pattern: FilenamePattern) } } +fn storage_backend_name(locale: &LanguageIdentifier, backend: StorageBackend) -> Cow { + match backend { + StorageBackend::Disk => text(locale, "storage-backend-disk"), + StorageBackend::Memory => text(locale, "storage-backend-memory"), + } +} + fn backend_availability(instance: &wgpu::Instance, backend: wgpu::Backends) -> wgpu::Backends { if instance.enumerate_adapters(backend).is_empty() { wgpu::Backends::empty() diff --git a/desktop/src/player.rs b/desktop/src/player.rs index f581ad0c7..75d08d9d2 100644 --- a/desktop/src/player.rs +++ b/desktop/src/player.rs @@ -1,6 +1,6 @@ use crate::backends::{ CpalAudioBackend, DesktopExternalInterfaceProvider, DesktopFSCommandProvider, DesktopUiBackend, - DiskStorageBackend, ExternalNavigatorBackend, + ExternalNavigatorBackend, }; use crate::custom_event::RuffleEvent; use crate::executor::WinitAsyncExecutor; @@ -159,7 +159,7 @@ impl ActivePlayer { builder = builder .with_navigator(navigator) .with_renderer(renderer) - .with_storage(DiskStorageBackend::new(opt.save_directory.clone())) + .with_storage(preferences.storage_backend().create_backend(opt)) .with_fs_commands(Box::new(DesktopFSCommandProvider { event_loop: event_loop.clone(), window: window.clone(), diff --git a/desktop/src/preferences.rs b/desktop/src/preferences.rs index ac6540c11..0413520fb 100644 --- a/desktop/src/preferences.rs +++ b/desktop/src/preferences.rs @@ -1,6 +1,8 @@ mod read; mod write; +pub mod storage; + use crate::cli::Opt; use crate::log::FilenamePattern; use crate::preferences::read::{read_bookmarks, read_preferences}; @@ -165,6 +167,17 @@ impl GlobalPreferences { !bookmarks.is_empty() && !bookmarks.iter().all(|x| x.is_invalid()) } + pub fn storage_backend(&self) -> storage::StorageBackend { + self.cli.storage.unwrap_or_else(|| { + self.preferences + .lock() + .expect("Preferences is not reentrant") + .values + .storage + .backend + }) + } + pub fn write_preferences(&self, fun: impl FnOnce(&mut PreferencesWriter)) -> Result<(), Error> { let mut preferences = self .preferences @@ -226,6 +239,7 @@ pub struct SavedGlobalPreferences { pub mute: bool, pub volume: f32, pub log: LogPreferences, + pub storage: StoragePreferences, } impl Default for SavedGlobalPreferences { @@ -242,6 +256,7 @@ impl Default for SavedGlobalPreferences { mute: false, volume: 1.0, log: Default::default(), + storage: Default::default(), } } } @@ -251,6 +266,11 @@ pub struct LogPreferences { pub filename_pattern: FilenamePattern, } +#[derive(PartialEq, Debug, Default)] +pub struct StoragePreferences { + pub backend: storage::StorageBackend, +} + pub static INVALID_URL: &str = "invalid:///"; #[derive(Debug, PartialEq)] diff --git a/desktop/src/preferences/read.rs b/desktop/src/preferences/read.rs index 5acbe56bc..2855fae48 100644 --- a/desktop/src/preferences/read.rs +++ b/desktop/src/preferences/read.rs @@ -244,6 +244,12 @@ pub fn read_preferences(input: &str) -> (ParseResult, Do }; }); + document.get_table_like(&mut cx, "storage", |cx, storage| { + if let Some(value) = storage.parse_from_str(cx, "backend") { + result.result.storage.backend = value; + } + }); + result.warnings = cx.warnings; (result, document) } @@ -290,7 +296,7 @@ pub fn read_bookmarks(input: &str) -> (ParseResult>, DocumentMut) mod tests { use super::*; use crate::log::FilenamePattern; - use crate::preferences::LogPreferences; + use crate::preferences::{storage::StorageBackend, LogPreferences, StoragePreferences}; use fluent_templates::loader::langid; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use url::Url; @@ -604,6 +610,63 @@ mod tests { ); } + #[test] + fn storage_backend() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + storage: StoragePreferences { + ..Default::default() + }, + ..Default::default() + }, + warnings: vec![ + "Invalid storage.backend: expected string but found integer".to_string() + ] + }, + read_preferences("storage = {backend = 5}").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + storage: StoragePreferences { + ..Default::default() + }, + ..Default::default() + }, + warnings: vec!["Invalid storage.backend: unsupported value \"???\"".to_string()] + }, + read_preferences("storage = {backend = \"???\"}").0 + ); + + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + storage: StoragePreferences { + backend: StorageBackend::Memory, + }, + ..Default::default() + }, + warnings: vec![] + }, + read_preferences("storage = {backend = \"memory\"}").0 + ); + } + + #[test] + fn storage() { + assert_eq!( + ParseResult { + result: SavedGlobalPreferences { + ..Default::default() + }, + warnings: vec!["Invalid storage: expected table but found string".to_string()] + }, + read_preferences("storage = \"no\"").0 + ); + } + #[test] fn bookmark() { assert_eq!( diff --git a/desktop/src/preferences/storage.rs b/desktop/src/preferences/storage.rs new file mode 100644 index 000000000..8119b380c --- /dev/null +++ b/desktop/src/preferences/storage.rs @@ -0,0 +1,41 @@ +use crate::{backends::DiskStorageBackend, player::PlayerOptions}; +use ruffle_core::backend::storage::MemoryStorageBackend; +use std::str::FromStr; + +#[derive(clap::ValueEnum, Copy, Clone, PartialEq, Eq, Debug, Default)] +pub enum StorageBackend { + #[default] + Disk, + Memory, +} + +impl FromStr for StorageBackend { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "disk" => Ok(StorageBackend::Disk), + "memory" => Ok(StorageBackend::Memory), + _ => Err(()), + } + } +} + +impl StorageBackend { + pub fn as_str(&self) -> &'static str { + match self { + StorageBackend::Disk => "disk", + StorageBackend::Memory => "memory", + } + } + + pub fn create_backend( + &self, + opt: &PlayerOptions, + ) -> Box { + match self { + StorageBackend::Disk => Box::new(DiskStorageBackend::new(opt.save_directory.clone())), + StorageBackend::Memory => Box::new(MemoryStorageBackend::new()), + } + } +} diff --git a/desktop/src/preferences/write.rs b/desktop/src/preferences/write.rs index 9edcb2c7a..8d56fcf47 100644 --- a/desktop/src/preferences/write.rs +++ b/desktop/src/preferences/write.rs @@ -1,4 +1,5 @@ use crate::log::FilenamePattern; +use crate::preferences::storage::StorageBackend; use crate::preferences::{Bookmark, BookmarksAndDocument, PreferencesAndDocument}; use ruffle_render_wgpu::clap::{GraphicsBackend, PowerPreference}; use toml_edit::{array, value, ArrayOfTables, Table}; @@ -49,6 +50,11 @@ impl<'a> PreferencesWriter<'a> { self.0.toml_document["log"]["filename_pattern"] = value(pattern.as_str()); self.0.values.log.filename_pattern = pattern; } + + pub fn set_storage_backend(&mut self, backend: StorageBackend) { + self.0.toml_document["storage"]["backend"] = value(backend.as_str()); + self.0.values.storage.backend = backend; + } } pub struct BookmarksWriter<'a>(&'a mut BookmarksAndDocument); @@ -242,6 +248,25 @@ mod tests { "[log]\nfilename_pattern = \"single_file\"\n", ); } + + #[test] + fn set_storage_backend() { + test( + "", + |writer| writer.set_storage_backend(StorageBackend::Disk), + "storage = { backend = \"disk\" }\n", + ); + test( + "storage = { backend = \"disk\" }\n", + |writer| writer.set_storage_backend(StorageBackend::Memory), + "storage = { backend = \"memory\" }\n", + ); + test( + "[storage]\nbackend = \"disk\"\n", + |writer| writer.set_storage_backend(StorageBackend::Memory), + "[storage]\nbackend = \"memory\"\n", + ); + } } #[allow(clippy::unwrap_used)] diff --git a/web/src/lib.rs b/web/src/lib.rs index 74b4adc10..e55afde25 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -644,7 +644,7 @@ impl Ruffle { match window.local_storage() { Ok(Some(s)) => { - builder = builder.with_storage(storage::LocalStorageBackend::new(s)); + builder = builder.with_storage(Box::new(storage::LocalStorageBackend::new(s))); } err => { tracing::warn!("Unable to use localStorage: {:?}\nData will not save.", err);