use std::{any::TypeId, marker::PhantomData, path::PathBuf};
use bevy::{
app::{App, Plugin, PostUpdate, Startup},
ecs::{
component::Component,
schedule::{IntoScheduleConfigs, SystemSet},
system::{Commands, Query},
world::{CommandQueue, World},
},
log::warn,
prelude::Resource,
reflect::{
GetTypeRegistration, Reflect, TypePath, TypeRegistry,
serde::{TypedReflectDeserializer, TypedReflectSerializer},
},
tasks::{Task, futures::check_ready},
};
pub use bevy_simple_prefs_derive::*;
use ron::ser::{PrettyConfig, to_string_pretty};
use serde::de::DeserializeSeed;
pub trait Prefs {
fn init(app: &mut App);
fn save(world: &mut World);
fn load(world: &mut World);
}
pub struct PrefsPlugin<T: Reflect + TypePath> {
pub path: PathBuf,
pub local_storage_key: String,
pub _phantom: PhantomData<T>,
}
impl<T: Reflect + TypePath> Default for PrefsPlugin<T> {
fn default() -> Self {
let package_name = T::crate_name().unwrap_or("bevy_simple");
let file_name = format!("{}_prefs.ron", package_name);
Self {
path: file_name.into(),
local_storage_key: format!("{package_name}::{}.ron", T::short_type_path()),
_phantom: Default::default(),
}
}
}
#[derive(Resource)]
pub struct PrefsSettings<T> {
pub local_storage_key: String,
pub path: PathBuf,
pub _phantom: PhantomData<T>,
}
#[derive(Resource)]
pub struct PrefsStatus<T> {
pub loaded: bool,
_phantom: PhantomData<T>,
}
impl<T> Default for PrefsStatus<T> {
fn default() -> Self {
Self {
loaded: false,
_phantom: Default::default(),
}
}
}
#[derive(Component)]
pub struct LoadPrefsTask(pub Task<CommandQueue>);
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct PrefsSaveSystems;
#[derive(Resource, Default)]
struct HandleTasksSystemAdded;
impl<T: Prefs + Reflect + TypePath> Plugin for PrefsPlugin<T> {
fn build(&self, app: &mut bevy::prelude::App) {
app.insert_resource::<PrefsSettings<T>>(PrefsSettings {
path: self.path.clone(),
local_storage_key: self.local_storage_key.clone(),
_phantom: Default::default(),
});
app.init_resource::<PrefsStatus<T>>();
<T>::init(app);
if app
.world()
.get_resource::<HandleTasksSystemAdded>()
.is_none()
{
app.add_systems(PostUpdate, handle_tasks.before(PrefsSaveSystems));
app.init_resource::<HandleTasksSystemAdded>();
}
app.add_systems(PostUpdate, <T>::save.in_set(PrefsSaveSystems));
app.add_systems(Startup, <T>::load);
}
}
fn handle_tasks(mut commands: Commands, mut transform_tasks: Query<&mut LoadPrefsTask>) {
for mut task in &mut transform_tasks {
if let Some(mut commands_queue) = check_ready(&mut task.0) {
bevy::log::debug!("Adding Resource update commands to queue");
commands.append(&mut commands_queue);
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub fn load_str(path: &std::path::Path) -> Option<String> {
std::fs::read_to_string(path).ok()
}
#[cfg(target_arch = "wasm32")]
pub fn load_str(local_storage_key: &str) -> Option<String> {
let Some(window) = web_sys::window() else {
warn!("Failed to load save file: no window.");
return None;
};
let Ok(Some(storage)) = window.local_storage() else {
warn!("Failed to load save file: no storage.");
return None;
};
let Ok(maybe_item) = storage.get_item(local_storage_key) else {
warn!("Failed to load save file: failed to get item.");
return None;
};
maybe_item
}
#[cfg(not(target_arch = "wasm32"))]
pub fn save_str(path: &std::path::Path, data: &str) {
if let Err(e) = std::fs::write(path, data) {
warn!("Failed to store save file: {:?}", e);
}
}
#[cfg(target_arch = "wasm32")]
pub fn save_str(local_storage_key: &str, data: &str) {
let Some(window) = web_sys::window() else {
warn!("Failed to store save file: no window.");
return;
};
let Ok(Some(storage)) = window.local_storage() else {
warn!("Failed to store save file: no storage.");
return;
};
if let Err(e) = storage.set_item(local_storage_key, data) {
warn!("Failed to store save file: {:?}", e);
}
}
pub fn deserialize<T: Reflect + GetTypeRegistration + Default>(
serialized: &str,
) -> Result<T, ron::de::Error> {
let mut registry = TypeRegistry::new();
registry.register::<T>();
let registration = registry.get(TypeId::of::<T>()).unwrap();
let mut deserializer = ron::Deserializer::from_str(serialized).unwrap();
let de = TypedReflectDeserializer::new(registration, ®istry);
let dynamic_struct = de.deserialize(&mut deserializer)?;
let mut val = T::default();
val.apply(&*dynamic_struct);
Ok(val)
}
pub fn serialize<T: Reflect + GetTypeRegistration>(to_save: &T) -> Result<String, ron::Error> {
let mut registry = TypeRegistry::new();
registry.register::<T>();
let config = PrettyConfig::default();
let reflect_serializer = TypedReflectSerializer::new(to_save, ®istry);
to_string_pretty(&reflect_serializer, config)
}