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 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"); }
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}