use bevy::prelude::*;
use std::collections::HashMap;
use std::path::PathBuf;
use super::components::SaveMetadata;
#[derive(Resource, Clone, Reflect)]
#[reflect(Resource)]
pub struct SaveLoadConfig {
pub save_directory: String,
pub enable_auto_save: bool,
pub auto_save_period: u32,
pub max_save_slots: usize,
pub quicksave_slot: String,
}
impl Default for SaveLoadConfig {
fn default() -> Self {
Self {
save_directory: "./saves".to_string(),
enable_auto_save: false,
auto_save_period: 1,
max_save_slots: 10,
quicksave_slot: "quicksave".to_string(),
}
}
}
impl SaveLoadConfig {
pub fn slot_path(&self, slot_name: &str) -> PathBuf {
PathBuf::from(&self.save_directory).join(format!("{}.ron", slot_name))
}
pub fn ensure_save_directory(&self) -> std::io::Result<()> {
std::fs::create_dir_all(&self.save_directory)
}
}
#[derive(Clone, Debug, Reflect)]
#[reflect(opaque)]
pub struct SaveSlotInfo {
pub slot_name: String,
pub file_path: String,
pub metadata: SaveMetadata,
pub file_size: u64,
}
impl SaveSlotInfo {
pub fn from_file(file_path: impl Into<PathBuf>) -> std::io::Result<Self> {
let path: PathBuf = file_path.into();
let slot_name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let content = std::fs::read_to_string(&path)?;
let metadata = ron::from_str::<SaveMetadata>(&content)
.unwrap_or_else(|_| SaveMetadata::new(&slot_name));
let file_size = std::fs::metadata(&path)?.len();
Ok(Self {
slot_name,
file_path: path.to_string_lossy().to_string(),
metadata,
file_size,
})
}
}
#[derive(Resource, Default, Reflect)]
#[reflect(Resource)]
pub struct SaveSlotRegistry {
#[reflect(ignore)]
slots: HashMap<String, SaveSlotInfo>,
}
impl SaveSlotRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn add_slot(&mut self, slot_info: SaveSlotInfo) {
self.slots.insert(slot_info.slot_name.clone(), slot_info);
}
pub fn remove_slot(&mut self, slot_name: &str) {
self.slots.remove(slot_name);
}
pub fn get_slot(&self, slot_name: &str) -> Option<&SaveSlotInfo> {
self.slots.get(slot_name)
}
pub fn all_slots(&self) -> Vec<&SaveSlotInfo> {
self.slots.values().collect()
}
pub fn has_slot(&self, slot_name: &str) -> bool {
self.slots.contains_key(slot_name)
}
pub fn refresh_from_disk(&mut self, config: &SaveLoadConfig) -> std::io::Result<()> {
self.slots.clear();
config.ensure_save_directory()?;
let save_dir = PathBuf::from(&config.save_directory);
if !save_dir.exists() {
return Ok(());
}
for entry in std::fs::read_dir(save_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("ron") {
if let Ok(slot_info) = SaveSlotInfo::from_file(&path) {
self.add_slot(slot_info);
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_default() {
let config = SaveLoadConfig::default();
assert_eq!(config.save_directory, "./saves");
assert!(!config.enable_auto_save);
assert_eq!(config.auto_save_period, 1);
assert_eq!(config.max_save_slots, 10);
assert_eq!(config.quicksave_slot, "quicksave");
}
#[test]
fn test_config_slot_path() {
let config = SaveLoadConfig::default();
let path = config.slot_path("slot_1");
assert_eq!(path, PathBuf::from("./saves/slot_1.ron"));
}
#[test]
fn test_save_slot_registry() {
let mut registry = SaveSlotRegistry::new();
let slot_info = SaveSlotInfo {
slot_name: "slot_1".to_string(),
file_path: "./saves/slot_1.ron".to_string(),
metadata: SaveMetadata::new("slot_1"),
file_size: 1024,
};
registry.add_slot(slot_info);
assert!(registry.has_slot("slot_1"));
assert_eq!(registry.all_slots().len(), 1);
registry.remove_slot("slot_1");
assert!(!registry.has_slot("slot_1"));
}
}