geng_asset/
lib.rs

1use batbox_file as file;
2use futures::prelude::*;
3use geng_shader as shader;
4use std::cell::RefCell;
5use std::future::Future as StdFuture;
6use std::path::{Path, PathBuf};
7use std::pin::Pin;
8use std::rc::Rc;
9use std::sync::Arc;
10use ugli::Ugli;
11
12pub mod hot;
13mod platform;
14
15pub use hot::Hot;
16
17pub use geng_asset_derive::*;
18
19pub type Future<T> = Pin<Box<dyn StdFuture<Output = anyhow::Result<T>>>>;
20
21struct ManagerImpl {
22    ugli: Ugli,
23    window: geng_window::Window,
24    #[cfg(feature = "audio")]
25    audio: geng_audio::Audio,
26    shader_lib: shader::Library,
27    hot_reload_enabled: bool,
28}
29
30#[derive(Clone)]
31pub struct Manager {
32    inner: Rc<ManagerImpl>,
33}
34
35impl Manager {
36    pub fn new(
37        window: &geng_window::Window,
38        #[cfg(feature = "audio")] audio: &geng_audio::Audio,
39        hot_reload: bool,
40    ) -> Self {
41        Self {
42            inner: Rc::new(ManagerImpl {
43                window: window.clone(),
44                ugli: window.ugli().clone(),
45                #[cfg(feature = "audio")]
46                audio: audio.clone(),
47                shader_lib: shader::Library::new(window.ugli(), true, None),
48                hot_reload_enabled: hot_reload,
49            }),
50        }
51    }
52    pub async fn yield_now(&self) {
53        self.inner.window.yield_now().await
54    }
55    #[cfg(feature = "audio")]
56    pub fn audio(&self) -> &geng_audio::Audio {
57        &self.inner.audio
58    }
59    pub fn ugli(&self) -> &Ugli {
60        &self.inner.ugli
61    }
62    pub fn shader_lib(&self) -> &shader::Library {
63        &self.inner.shader_lib
64    }
65    pub fn hot_reload_enabled(&self) -> bool {
66        self.inner.hot_reload_enabled
67    }
68    pub fn load<T: Load>(&self, path: impl AsRef<Path>) -> Future<T> {
69        T::load(self, path.as_ref(), &Default::default())
70    }
71    pub fn load_with<T: Load>(&self, path: impl AsRef<Path>, options: &T::Options) -> Future<T> {
72        T::load(self, path.as_ref(), options)
73    }
74    /// Load asset from given path with specified or default extension
75    pub fn load_ext<T: Load>(
76        &self,
77        path: impl AsRef<std::path::Path>,
78        options: &T::Options,
79        ext: Option<impl AsRef<str>>,
80    ) -> Future<T> {
81        let path = path.as_ref();
82        let path_buf_tmp;
83        let path = match ext.as_ref().map(|s| s.as_ref()).or(T::DEFAULT_EXT) {
84            Some(ext) => {
85                path_buf_tmp = path.with_extension(ext);
86                &path_buf_tmp
87            }
88            None => path,
89        };
90        self.load_with(path, options)
91    }
92}
93
94pub trait Collection {
95    type Item;
96}
97
98impl<T> Collection for Vec<T> {
99    type Item = T;
100}
101
102pub trait Load: Sized + 'static {
103    type Options: Clone + Default;
104    fn load(manager: &Manager, path: &Path, options: &Self::Options) -> Future<Self>;
105    const DEFAULT_EXT: Option<&'static str>;
106}
107
108impl<T: 'static> Load for Rc<T>
109where
110    T: Load,
111{
112    type Options = T::Options;
113    fn load(manager: &Manager, path: &Path, options: &Self::Options) -> Future<Self> {
114        let inner = T::load(manager, path, options);
115        async move { Ok(Rc::new(inner.await?)) }.boxed_local()
116    }
117    const DEFAULT_EXT: Option<&'static str> = T::DEFAULT_EXT;
118}
119
120impl Load for ugli::Program {
121    type Options = ();
122    fn load(manager: &Manager, path: &Path, _options: &Self::Options) -> Future<Self> {
123        let glsl: Future<String> = manager.load(path);
124        let manager = manager.clone();
125        async move {
126            let glsl: String = glsl.await?;
127            manager.shader_lib().compile(&glsl)
128        }
129        .boxed_local()
130    }
131    const DEFAULT_EXT: Option<&'static str> = Some("glsl");
132}
133
134impl Load for serde_json::Value {
135    type Options = ();
136    fn load(manager: &Manager, path: &Path, _options: &Self::Options) -> Future<Self> {
137        let string: Future<String> = manager.load(path);
138        async move {
139            let string: String = string.await?;
140            Ok(serde_json::from_str(&string)?)
141        }
142        .boxed_local()
143    }
144    const DEFAULT_EXT: Option<&'static str> = Some("json");
145}
146
147impl Load for String {
148    type Options = ();
149    fn load(_manager: &Manager, path: &Path, _options: &Self::Options) -> Future<Self> {
150        let path = path.to_owned();
151        async move { file::load_string(&path).await }.boxed_local()
152    }
153    const DEFAULT_EXT: Option<&'static str> = Some("txt");
154}
155
156impl Load for Vec<u8> {
157    type Options = ();
158    fn load(_manager: &Manager, path: &Path, _options: &Self::Options) -> Future<Self> {
159        let path = path.to_owned();
160        async move { file::load_bytes(&path).await }.boxed_local()
161    }
162    const DEFAULT_EXT: Option<&'static str> = None;
163}
164
165impl Load for geng_font::Font {
166    type Options = geng_font::Options;
167    fn load(manager: &Manager, path: &Path, options: &Self::Options) -> Future<Self> {
168        let manager = manager.clone();
169        let path = path.to_owned();
170        let options = options.clone();
171        async move {
172            let data = file::load_bytes(path).await?;
173            geng_font::Font::new(manager.ugli(), &data, &options)
174        }
175        .boxed_local()
176    }
177    const DEFAULT_EXT: Option<&'static str> = Some("ttf");
178}
179
180#[derive(Debug)]
181pub struct LoadProgress {
182    pub progress: usize,
183    pub total: usize,
184}
185
186impl LoadProgress {
187    pub fn new() -> Self {
188        Self {
189            progress: 0,
190            total: 0,
191        }
192    }
193}
194
195impl Default for LoadProgress {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201#[cfg(feature = "audio")]
202#[derive(Debug, Clone)]
203pub struct SoundOptions {
204    pub looped: bool,
205}
206
207#[cfg(feature = "audio")]
208#[allow(clippy::derivable_impls)]
209impl Default for SoundOptions {
210    fn default() -> Self {
211        Self { looped: false }
212    }
213}
214
215#[cfg(feature = "audio")]
216impl Load for geng_audio::Sound {
217    type Options = SoundOptions;
218    fn load(manager: &Manager, path: &std::path::Path, options: &Self::Options) -> Future<Self> {
219        let manager = manager.clone();
220        let path = path.to_owned();
221        let options = options.clone();
222        Box::pin(async move {
223            let mut sound = manager.audio().load(path).await?;
224            sound.set_looped(options.looped);
225            Ok(sound)
226        })
227    }
228    const DEFAULT_EXT: Option<&'static str> = Some("wav"); // TODO change to mp3 since wav doesnt work in safari?
229}
230
231#[derive(Debug, Clone)]
232pub struct TextureOptions {
233    pub filter: ugli::Filter,
234    pub wrap_mode: ugli::WrapMode,
235    pub premultiply_alpha: bool,
236}
237
238impl Default for TextureOptions {
239    fn default() -> Self {
240        Self {
241            filter: ugli::Filter::Linear,
242            wrap_mode: ugli::WrapMode::Clamp,
243            premultiply_alpha: false,
244        }
245    }
246}
247
248impl Load for ugli::Texture {
249    type Options = TextureOptions;
250    fn load(manager: &Manager, path: &std::path::Path, options: &Self::Options) -> Future<Self> {
251        let manager = manager.clone();
252        let path = path.to_owned();
253        let options = options.clone();
254        async move {
255            let mut texture = platform::load_texture(&manager, &path, &options).await?;
256            texture.set_filter(options.filter);
257            texture.set_wrap_mode(options.wrap_mode);
258            Ok(texture)
259        }
260        .boxed_local()
261    }
262    const DEFAULT_EXT: Option<&'static str> = Some("png");
263}
264
265pub trait Optional {
266    type Type;
267}
268
269impl<T> Optional for Option<T> {
270    type Type = T;
271}