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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
use image::{DynamicImage, GenericImageView};
use glium::texture::RawImage2d;
use std::collections::HashMap;
use std::fs::File;
use std::io::BufReader;
use serde_json::Value;
use Value::Object;
use glium::backend::Facade;

struct Texture {
    pub image: DynamicImage,
}

impl Texture {
    pub fn from_file(path: &str) -> Texture {
        Texture {
            image: image::open(path).unwrap(),
        }
    }

    pub fn as_raw_image_2d(&self) -> RawImage2d<u8> {
        return glium::texture::RawImage2d::from_raw_rgba_reversed(&self.image.to_rgba8().into_raw(), self.image.dimensions());
    }
}

const DEFAULT_CONFIG_PATH: &str = "texture_config.json";

/// Common structure storage with all the data to operate textures in project.
/// Encapsulates loading logic, supports lazy loading.
/// Note: by default, all config keys are String types.
/// Todo: remake it on more generic usage with any displayable type, not only string.
pub struct TextureBag {
    /// Data read by config. For now Hashmap of texture ID -> path to texture.
    /// It might change until first stable version, but interfaces won't be affected.
    config_data: HashMap<String, String>,

    /// Textures as glium::texture::Texture2d. Will extend it to other texture types if needed.
    textures: HashMap<String, glium::texture::Texture2d>,
}

impl TextureBag {
    /// Loads all textures described in config into memory
    /// # Panics
    ///
    /// Method will panic if config file is missing.
    pub fn init_eager<F>(facade: &F, config_path: Option<String>)
                         -> TextureBag where F: Facade
    {
        let path = match config_path {
            Some(path) => path,
            None => String::from(DEFAULT_CONFIG_PATH),
        };

        let file = File::open(&path).unwrap();
        let buffered_reader = BufReader::new(file);
        let file_data: Value = serde_json::from_reader(buffered_reader).unwrap();
        let loaded_textures_config = file_data.get("textures").unwrap();
        let mut converted_config: HashMap<String, String> = HashMap::new();

        match loaded_textures_config {
            Object(config) => {
                for entry in config.clone() {
                    match entry.1 {
                        Value::String(path) => {
                            converted_config.insert(entry.0, path);
                        }
                        _ => panic!("Expected texture path for texture ID {}", entry.0)
                    }
                }
            }
            _ => panic!("Invalid JSON structure. Expected 'textures' key-value object.")
        }

        let mut loaded_textures: HashMap<String, glium::texture::Texture2d> = HashMap::new();

        for texture in converted_config.clone() {
            loaded_textures.insert(
                texture.0,
                glium::texture::Texture2d::new(facade, Texture::from_file(texture.1.as_str()).as_raw_image_2d()).unwrap()
            );
        }

        TextureBag {
            config_data: converted_config,
            textures: loaded_textures
        }
    }

    /// # Panics
    ///
    /// Method will panic if config file is missing or if json format is invalid.
    pub fn init_lazy<F>(_facade: &F, config_path: Option<String>)
        -> TextureBag where F: Facade
    {
        let path = match config_path {
            Some(path) => path,
            None => String::from(DEFAULT_CONFIG_PATH),
        };

        let file = File::open(&path).unwrap();
        let buffered_reader = BufReader::new(file);
        let file_data: Value = serde_json::from_reader(buffered_reader).unwrap();
        let loaded_textures_config = file_data.get("textures").unwrap();
        let mut converted_config: HashMap<String, String> = HashMap::new();

        match loaded_textures_config {
            Object(config) => {
                for entry in config.clone() {
                    match entry.1 {
                        Value::String(path) => {
                            converted_config.insert(entry.0, path);
                        }
                        _ => panic!("Expected texture path for texture ID {}", entry.0)
                    }
                }
            }
            _ => panic!("Invalid JSON structure. Expected 'textures' key-value object.")
        }

        TextureBag {
            config_data: converted_config,
            textures: HashMap::new(),
        }
    }

    /// Method tries to get texture from existing map.
    /// If texture was not found in map, method will try to get it's path from config,
    /// load texture and put it in map. Texture will also be returned.
    /// Display variable won't be used if texture was initialized on load.
    /// # Panics
    ///
    /// Method will panic if texture file is missing.
    pub fn get_texture<F>(&mut self, texture_id: String, facade: &F)
                          -> &glium::texture::Texture2d where F: Facade
    {
        if self.textures.get(&texture_id).is_none() {
            let texture_path = match self.config_data.get(&texture_id) {
                Some(path) => path.clone(),
                None => panic!("Unknown texture_id provided to bag: {}", &texture_id),
            };
            let loaded_texture = glium::texture::Texture2d::new(facade, Texture::from_file(texture_path.as_str()).as_raw_image_2d()).unwrap();
            self.textures.insert(
                texture_id.clone(),
                loaded_texture
            );
        }

        return self.textures.get(&texture_id).unwrap();
    }

    /// Removes texture from memory. Texture will be reloaded on next get_texture call.
    pub fn forget(&mut self, texture_id: String) {
        self.textures.remove(texture_id.as_str());
    }
}