use bevy::{
app::App,
ecs::{
resource::Resource,
world::FromWorld,
},
tasks::{
ConditionalSend,
ConditionalSendFuture,
},
};
use serde::{
Serialize,
de::DeserializeSeed,
};
use crate::{
error::Error,
prelude::*,
};
#[derive(Resource)]
pub struct AppBackend<B>(pub B);
pub trait Backend<K> {
fn save<F: Format, T: Serialize + ConditionalSend + Sync>(
&self,
key: K,
value: &T,
) -> impl ConditionalSendFuture<Output = Result<(), Error>>;
fn load<F: Format, S: for<'de> DeserializeSeed<'de, Value = T> + ConditionalSend, T>(
&self,
key: K,
seed: S,
) -> impl ConditionalSendFuture<Output = Result<T, Error>>;
}
pub trait AppBackendExt {
fn insert_backend<B, K>(&mut self, backend: B) -> &mut Self
where
B: Backend<K> + Send + Sync + 'static;
fn init_backend<B, K>(&mut self) -> &mut Self
where
B: FromWorld + Backend<K> + Send + Sync + 'static;
}
impl AppBackendExt for App {
fn insert_backend<B, K>(&mut self, backend: B) -> &mut Self
where
B: Backend<K> + Send + Sync + 'static,
{
self.insert_resource(AppBackend(backend))
}
fn init_backend<B, K>(&mut self) -> &mut Self
where
B: FromWorld + Backend<K> + Send + Sync + 'static,
{
let backend = B::from_world(self.world_mut());
self.insert_backend(backend)
}
}
#[cfg(not(target_arch = "wasm32"))]
mod desktop {
use bevy::prelude::*;
use smol::{
fs::{
File,
create_dir_all,
},
io::{
AsyncReadExt,
AsyncWriteExt,
},
};
#[allow(clippy::wildcard_imports)]
use super::*;
#[derive(Default)]
pub struct FileIO;
impl<K: std::fmt::Display + Send> Backend<K> for FileIO {
async fn save<F: Format, T: Serialize>(&self, key: K, value: &T) -> Result<(), Error> {
let path = get_save_file(format!("{key}{}", F::extension()));
let dir = path.parent().expect("Invalid save directory");
create_dir_all(dir).await?;
let mut buf = Vec::new();
F::serialize(&mut buf, value)?;
let mut file = File::create(path).await?;
Ok(file.write_all(&buf).await?)
}
async fn load<F: Format, S: for<'de> DeserializeSeed<'de, Value = T>, T>(
&self,
key: K,
seed: S,
) -> Result<T, Error> {
let path = get_save_file(format!("{key}{}", F::extension()));
let mut file = File::open(path).await?;
let mut buf = Vec::new();
file.read_to_end(&mut buf).await?;
F::deserialize(&*buf, seed)
}
}
#[derive(Default)]
pub struct DebugFileIO;
impl<K: std::fmt::Display + Send> Backend<K> for DebugFileIO {
async fn save<F: Format, T: Serialize>(&self, key: K, value: &T) -> Result<(), Error> {
let mut buf = Vec::new();
F::serialize(&mut buf, value)?;
let mut file = File::create(format!("{key}{}", F::extension())).await?;
Ok(file.write_all(&buf).await?)
}
async fn load<F: Format, S: for<'de> DeserializeSeed<'de, Value = T>, T>(
&self,
key: K,
seed: S,
) -> Result<T, Error> {
let mut file = File::open(format!("{key}{}", F::extension())).await?;
let mut buf = Vec::new();
file.read_to_end(&mut buf).await?;
F::deserialize(&*buf, seed)
}
}
}
#[cfg(not(target_arch = "wasm32"))]
pub use desktop::FileIO;
#[cfg(not(target_arch = "wasm32"))]
pub type DefaultBackend = desktop::FileIO;
#[cfg(not(target_arch = "wasm32"))]
pub type DefaultDebugBackend = desktop::DebugFileIO;
#[cfg(target_arch = "wasm32")]
mod wasm {
use bevy::prelude::*;
use fragile::Fragile;
use serde::{
Serialize,
de::DeserializeSeed,
};
use web_sys::Storage;
use crate::prelude::*;
pub struct WebStorage {
storage: Fragile<Storage>,
}
impl Default for WebStorage {
fn default() -> Self {
Self {
storage: Fragile::new(
web_sys::window()
.expect("No window")
.local_storage()
.expect("Failed to get local storage")
.expect("No local storage"),
),
}
}
}
impl<K> Backend<K> for WebStorage
where
K: AsRef<str>,
{
async fn save<F: Format, T: Serialize>(&self, key: K, value: &T) -> Result<(), Error> {
let mut buf: Vec<u8> = Vec::new();
F::serialize(&mut buf, value)?;
self.storage
.get()
.set_item(
&format!("{WORKSPACE}.{}", key.as_ref()),
&serde_json::to_string(&buf).map_err(Error::saving)?,
)
.expect("Failed to save");
Ok(())
}
async fn load<F: Format, S: for<'de> DeserializeSeed<'de, Value = T>, T>(
&self,
key: K,
seed: S,
) -> Result<T, Error> {
let value = self
.storage
.get()
.get_item(&format!("{WORKSPACE}.{}", key.as_ref()))
.expect("Failed to load")
.ok_or(Error::custom("Invalid key"))?;
let buf: Vec<u8> = serde_json::from_str(&value).map_err(Error::loading)?;
F::deserialize(&*buf, seed)
}
}
}
#[cfg(target_arch = "wasm32")]
pub use wasm::WebStorage;
#[cfg(target_arch = "wasm32")]
pub type DefaultBackend = wasm::WebStorage;
#[cfg(target_arch = "wasm32")]
pub type DefaultDebugBackend = wasm::WebStorage;