1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use {
    crate::math::Extent,
    app_dirs::{get_app_root, AppDataType, AppDirsError, AppInfo},
    serde::{Deserialize, Serialize},
    std::{
        fs::{create_dir_all, read_to_string, File},
        io::{Error as IoError, ErrorKind, Write},
        path::PathBuf,
    },
    toml::{from_str, to_string_pretty},
};

/// The name of the config while while in debug mode
#[cfg(debug_assertions)]
const CONFIG_FILENAME: &str = "engine-debug.toml";

/// The name of the config while while in release mode
#[cfg(not(debug_assertions))]
const CONFIG_FILENAME: &str = "engine.toml";

pub fn get_program_root(name: &'static str, author: &'static str) -> Result<PathBuf, IoError> {
    // Converts the app_dirs crate AppDirsError to a regular IO Error
    match get_app_root(AppDataType::UserConfig, &AppInfo { name, author }) {
        Err(err) => Err(match err {
            AppDirsError::Io(err) => err,
            AppDirsError::InvalidAppInfo => IoError::from(ErrorKind::InvalidInput),
            AppDirsError::NotSupported => IoError::from(ErrorKind::InvalidData),
        }),
        Ok(res) => Ok(res),
    }
}

fn get_config_path(name: &'static str, author: &'static str) -> Result<PathBuf, IoError> {
    let program_root = get_program_root(name, author)?;

    Ok(program_root.join(CONFIG_FILENAME))
}

pub struct Config {
    data: Data,
    program_author: &'static str,
    program_name: &'static str,
}

#[derive(Clone, Default, Deserialize, Serialize)]
struct Data {
    fullscreen: Option<bool>,
    swapchain_len: Option<u32>,
    window_dimensions: Option<(usize, usize)>,
}

impl Config {
    pub fn read(program_name: &'static str, program_author: &'static str) -> Result<Self, IoError> {
        let config_path = get_config_path(program_name, program_author)?;
        Ok(if config_path.exists() {
            #[cfg(debug_assertions)]
            debug!("Loaded config {}", config_path.display());

            let config_file = read_to_string(&*config_path).unwrap_or_else(|_| {
                #[cfg(debug_assertions)]
                warn!("Engine config file read error, creating a new one");

                "".to_owned()
            });
            let config: Schema = from_str(&config_file).unwrap_or_default();

            Self {
                data: config.data,
                program_author,
                program_name,
            }
        } else {
            #[cfg(debug_assertions)]
            info!("Engine config file not found, creating a new one");

            let mut res = Self {
                data: Data::default(),
                program_author,
                program_name,
            };
            res.data.fullscreen = None;
            res.data.swapchain_len = Some(res.swapchain_len());
            res.data.window_dimensions = None;
            res.write()?;

            res
        })
    }

    /// The default value is windowed mode (false).
    pub fn fullscreen(&self) -> Option<bool> {
        self.data.fullscreen
    }

    /// Value will be in the range of [1,3]. The default value is 3.
    pub fn swapchain_len(&self) -> u32 {
        self.data.swapchain_len.unwrap_or(3).max(1).min(3)
    }

    /// The dimensions of the window if set, otherwise 1920x1080.
    pub fn window_dimensions(&self) -> Extent {
        self.data
            .window_dimensions
            .map(|dims| Extent::new(dims.0 as _, dims.1 as _))
            .unwrap_or_else(|| Extent::new(1920, 1080))
    }

    pub fn write(&self) -> Result<(), IoError> {
        let program_root = get_program_root(self.program_name, self.program_author)?;

        if !program_root.exists() {
            create_dir_all(&*program_root)?;
        }

        let config_path = get_config_path(self.program_name, self.program_author)?;
        let mut config_file = File::create(&*config_path)?;

        let toml = to_string_pretty(&Schema {
            data: self.data.clone(),
        });
        if toml.is_err() {
            return Err(IoError::from(ErrorKind::Other));
        }
        let toml = toml.unwrap();

        config_file.write_all(toml.as_bytes())?;

        Ok(())
    }
}

#[derive(Default, Deserialize, Serialize)]
struct Schema {
    #[serde(rename = "screen-13")]
    data: Data,
}