use std::{
env,
fs::{
self,
File,
},
io::{
BufReader,
Write,
},
path::{
Path,
PathBuf,
},
};
use bevy::{
prelude::*,
tasks::IoTaskPool,
};
use lazy_static::lazy_static;
use platform_dirs::AppDirs;
use rmp_serde::{
Deserializer,
Serializer,
};
use serde::{
de::DeserializeSeed,
Serialize,
};
use crate::{
Capture,
Rollbacks,
SaveableRegistry,
Snapshot,
SnapshotDeserializer,
SnapshotSerializer,
};
pub trait WorldSaveableExt: Sized {
fn snapshot(&self) -> Snapshot;
fn checkpoint(&mut self);
fn rollback(&mut self, checkpoints: isize);
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
fn deserialize<'de, D: serde::Deserializer<'de>>(
&mut self,
deserializer: D,
) -> Result<(), D::Error>;
fn save(&self, name: &str);
fn load(&mut self, name: &str);
}
lazy_static! {
static ref APP_NAME: String = {
env::var("OUT_DIR")
.ok()
.map(|v| Path::new(&v).to_path_buf())
.and_then(|path| {
for ancestor in path.ancestors() {
if let Some(last) = ancestor.file_name() {
if last == "target" {
return ancestor
.parent()
.and_then(|p| p.file_name())
.and_then(|p| p.to_str())
.map(|p| p.to_owned());
}
}
}
None
})
.expect("Could not find parent workspace.")
};
}
lazy_static! {
static ref SAVE_DIR: PathBuf = {
AppDirs::new(Some(&APP_NAME), true)
.unwrap()
.data_dir
.join("saves")
};
}
pub fn get_save_file(name: &str) -> PathBuf {
SAVE_DIR.join(format!("{name}.sav"))
}
impl WorldSaveableExt for World {
fn snapshot(&self) -> Snapshot {
let registry = self.resource::<AppTypeRegistry>();
let saveables = self.resource::<SaveableRegistry>();
let mut resources = Vec::new();
for reg in saveables.types() {
if let Some(res) = reg.data::<ReflectResource>() {
if let Some(reflect) = res.reflect(self) {
resources.push(reflect.clone_value());
}
}
}
let scene = DynamicScene::from_world(self, registry);
let capture = Capture { resources, scene };
let rollbacks = self.resource::<Rollbacks>().clone();
Snapshot { capture, rollbacks }
}
fn checkpoint(&mut self) {
let snap = self.snapshot().into_rollback(self);
let mut state = self.resource_mut::<Rollbacks>();
state.checkpoint(snap);
}
fn rollback(&mut self, checkpoints: isize) {
let mut state = self.resource_mut::<Rollbacks>();
if let Some(snap) = state.rollback(checkpoints).cloned() {
snap.rollback(self);
}
}
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let registry = self.resource::<AppTypeRegistry>();
let snap = self.snapshot();
let ser = SnapshotSerializer::new(&snap, registry);
ser.serialize(serializer)
}
fn deserialize<'de, D: serde::Deserializer<'de>>(
&mut self,
deserializer: D,
) -> Result<(), D::Error> {
let registry = self.resource::<AppTypeRegistry>().clone();
let reg = registry.read();
let de = SnapshotDeserializer::new(®);
let snap = de.deserialize(deserializer)?;
snap.apply(self);
Ok(())
}
fn save(&self, name: &str) {
let mut buf = Vec::new();
self.serialize(&mut Serializer::new(&mut buf))
.expect("Error serializing save");
let name = name.to_owned();
IoTaskPool::get()
.spawn(async move {
fs::create_dir_all(&*SAVE_DIR).expect("Could not create save directory");
File::create(get_save_file(&name))
.and_then(|mut file| file.write(buf.as_slice()))
.expect("Error writing save to file");
})
.detach();
}
fn load(&mut self, name: &str) {
let path = get_save_file(name);
let file = File::open(path).expect("Could not open save file");
let mut reader = BufReader::new(file);
self.deserialize(&mut Deserializer::new(&mut reader))
.expect("Error deserializing save from file");
}
}