bottomless_pit/
resource.rs

1//! Contains the code for the loading of all assets and [ResourceId]
2//!
3//! All Resources have two types of loading a `Blocking` method and a `Background`
4//! method. On native platforms (Mac, Windows, Linux) The blocking method will
5//! just read from the file system and your resource will be available
6//! immediately. Background loads resources on a diffrent thread and it will be
7//! done when its done. On Wasm all things are loaded asynchronously so the
8//! Blocking method is faked by not updating or redrawing the game untill
9//! all blocking resources are loaded. There are no platform diffrences
10//! for background loading.
11//! ```rust
12//! fn main() {
13//!     let engine = mut EngineBuilder::new().build();
14//!     
15//!     let texture: ResourceId<Texture> = Texture::new(&mut engine, "path.png", LoadingOp::Blocking);
16//!     // anything loaded in main will be ready on the first frame of the game
17//!     let material = MaterialBuilder::new().add_texture(texture).build();
18//!
19//!     let game = YourGame {
20//!         textured_material: material,
21//!     }
22//! }
23//!
24//! struct YourGame {
25//!     textured_material: Material,
26//! }
27//!
28//! impl Game for YourGame {
29//!     fn update(&mut self, engine: &mut Engine) {
30//!         if engine.is_key_pressed(Key::A) {
31//!             let texutre = Texture::new(engine, "path2.png", LoadingOp::Blocking);
32//!             // render and update wont be called until after the texture finishes loading
33//!         }
34//!     }
35//!
36//!     fn render<'pass, 'others>(&'others mut self, renderer: RenderInformation<'pass, 'others>) where 'others: 'pass {
37//!         // do stuff
38//!     }
39//! }
40//! ```
41//! Because of this stalling behavior it is recomended you do all your loading of assests in as large of chunks as possible.
42use std::collections::HashMap;
43use std::fmt::Debug;
44use std::marker::PhantomData;
45use std::num::NonZeroU64;
46use std::path::{Path, PathBuf};
47use std::sync::atomic::AtomicU64;
48
49use winit::event_loop::EventLoopProxy;
50
51use crate::engine_handle::BpEvent;
52use crate::shader::{IntermediateOptions, Shader};
53use crate::text::Font;
54use crate::texture::{SamplerType, Texture};
55
56#[cfg(not(target_arch = "wasm32"))]
57use futures::executor::ThreadPool;
58
59#[cfg(target_arch = "wasm32")]
60async fn web_read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, ReadError> {
61    use js_sys::Uint8Array;
62    use wasm_bindgen::JsCast;
63    use wasm_bindgen_futures::JsFuture;
64
65    let path = path.as_ref().as_os_str().to_str().unwrap();
66
67    match web_sys::window() {
68        Some(window) => {
69            let response_value = JsFuture::from(window.fetch_with_str(path)).await.unwrap();
70
71            let response: web_sys::Response = response_value.dyn_into().unwrap();
72
73            if !response.ok() {
74                Err(ReadError::ResponseError(
75                    response.status(),
76                    response.status_text(),
77                ))?;
78            }
79
80            let data = JsFuture::from(response.array_buffer().unwrap())
81                .await
82                .unwrap();
83            let bytes = Uint8Array::new(&data).to_vec();
84            Ok(bytes)
85        }
86        None => Err(ReadError::WindowError),
87    }
88}
89
90#[cfg(not(target_arch = "wasm32"))]
91pub(crate) async fn read<P: AsRef<Path>>(path: P) -> Result<Vec<u8>, ReadError> {
92    Ok(std::fs::read(path)?)
93}
94
95pub(crate) enum ReadError {
96    IoError(std::io::Error),
97    #[cfg(target_arch = "wasm32")]
98    ResponseError(u16, String),
99    #[cfg(target_arch = "wasm32")]
100    WindowError,
101}
102
103impl Debug for ReadError {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self {
106            Self::IoError(e) => write!(f, "IoError({:?})", e),
107            #[cfg(target_arch = "wasm32")]
108            Self::ResponseError(code, text) => write!(f, "ResponseError({:?}, {:?})", code, text),
109            #[cfg(target_arch = "wasm32")]
110            Self::WindowError => write!(f, "WindowError"),
111        }
112    }
113}
114
115impl From<std::io::Error> for ReadError {
116    fn from(value: std::io::Error) -> Self {
117        Self::IoError(value)
118    }
119}
120
121pub(crate) struct Loader {
122    blocked_loading: usize,
123    background_loading: usize,
124    #[cfg(not(target_arch = "wasm32"))]
125    pool: ThreadPool,
126}
127
128impl Loader {
129    pub fn new() -> Self {
130        Self {
131            background_loading: 0,
132            blocked_loading: 0,
133            #[cfg(not(target_arch = "wasm32"))]
134            pool: ThreadPool::new().unwrap(),
135        }
136    }
137
138    pub fn remove_item_loading(&mut self, loading_op: LoadingOp) {
139        match loading_op {
140            LoadingOp::Background => self.background_loading -= 1,
141            LoadingOp::Blocking => self.blocked_loading -= 1,
142        }
143    }
144
145    pub fn get_loading_resources(&self) -> usize {
146        self.background_loading + self.blocked_loading
147    }
148
149    #[cfg(target_arch = "wasm32")]
150    pub fn is_blocked(&self) -> bool {
151        self.blocked_loading > 0
152    }
153
154    pub fn load(&mut self, ip_resource: InProgressResource, proxy: EventLoopProxy<BpEvent>) {
155        match ip_resource.loading_op {
156            LoadingOp::Background => self.background_load(ip_resource, proxy),
157            LoadingOp::Blocking => self.blocking_load(ip_resource, proxy),
158        }
159    }
160
161    // just fs read that stuff man
162    // becuase this is all happening on the main thread stuff will be read in before
163    // render() is called
164    #[cfg(not(target_arch = "wasm32"))]
165    pub fn blocking_load(
166        &mut self,
167        ip_resource: InProgressResource,
168        proxy: EventLoopProxy<BpEvent>,
169    ) {
170        let data: Result<Vec<u8>, ReadError> = match std::fs::read(&ip_resource.path) {
171            Ok(d) => Ok(d),
172            Err(e) => Err(e.into()),
173        };
174
175        let resource = Resource::from_result(
176            data,
177            ip_resource.path,
178            ip_resource.id,
179            ip_resource.resource_type,
180            ip_resource.loading_op,
181        );
182        self.blocked_loading += 1;
183        proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
184    }
185
186    // request but flip flag :3
187    #[cfg(target_arch = "wasm32")]
188    pub fn blocking_load(
189        &mut self,
190        ip_resource: InProgressResource,
191        proxy: EventLoopProxy<BpEvent>,
192    ) {
193        use wasm_bindgen_futures::spawn_local;
194        self.blocked_loading += 1;
195        spawn_local(async move {
196            let result = web_read(&ip_resource.path).await;
197            let resource = Resource::from_result(
198                result,
199                ip_resource.path,
200                ip_resource.id,
201                ip_resource.resource_type,
202                ip_resource.loading_op,
203            );
204            proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
205        });
206    }
207
208    // threadpool / aysnc
209    pub fn background_load(
210        &mut self,
211        ip_resource: InProgressResource,
212        proxy: EventLoopProxy<BpEvent>,
213    ) {
214        self.background_loading += 1;
215        #[cfg(not(target_arch = "wasm32"))]
216        {
217            self.pool.spawn_ok(async move {
218                let result = read(&ip_resource.path).await;
219                let resource = Resource::from_result(
220                    result,
221                    ip_resource.path,
222                    ip_resource.id,
223                    ip_resource.resource_type,
224                    ip_resource.loading_op,
225                );
226                proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
227            });
228        }
229
230        #[cfg(target_arch = "wasm32")]
231        {
232            use wasm_bindgen_futures::spawn_local;
233            spawn_local(async move {
234                let result = web_read(&ip_resource.path).await;
235                let resource = Resource::from_result(
236                    result,
237                    ip_resource.path,
238                    ip_resource.id,
239                    ip_resource.resource_type,
240                    ip_resource.loading_op,
241                );
242                proxy.send_event(BpEvent::ResourceLoaded(resource)).unwrap();
243            });
244        }
245    }
246}
247
248#[derive(Debug)]
249pub(crate) struct Resource {
250    pub(crate) path: PathBuf,
251    pub(crate) data: Vec<u8>,
252    pub(crate) id: NonZeroU64,
253    pub(crate) resource_type: ResourceType,
254    pub(crate) loading_op: LoadingOp,
255}
256
257#[derive(Debug)]
258pub(crate) struct ResourceError {
259    pub(crate) error: ReadError,
260    _path: PathBuf,
261    pub(crate) id: NonZeroU64,
262    pub(crate) resource_type: ResourceType,
263    pub(crate) loading_op: LoadingOp,
264}
265
266impl Resource {
267    pub fn from_result(
268        result: Result<Vec<u8>, ReadError>,
269        path: PathBuf,
270        id: NonZeroU64,
271        resource_type: ResourceType,
272        loading_op: LoadingOp,
273    ) -> Result<Self, ResourceError> {
274        match result {
275            Ok(data) => Ok(Self {
276                path,
277                data,
278                id,
279                resource_type,
280                loading_op,
281            }),
282            Err(e) => Err(ResourceError {
283                error: e,
284                _path: path,
285                id,
286                resource_type,
287                loading_op,
288            }),
289        }
290    }
291}
292
293#[derive(Debug)]
294pub(crate) struct InProgressResource {
295    path: PathBuf,
296    id: NonZeroU64,
297    resource_type: ResourceType,
298    loading_op: LoadingOp,
299}
300
301impl InProgressResource {
302    pub fn new(
303        path: &Path,
304        id: NonZeroU64,
305        resource_type: ResourceType,
306        loading_op: LoadingOp,
307    ) -> Self {
308        Self {
309            path: path.to_owned(),
310            id,
311            resource_type,
312            loading_op,
313        }
314    }
315}
316
317#[derive(Debug)]
318pub(crate) enum ResourceType {
319    Image(SamplerType, SamplerType),
320    Shader(IntermediateOptions),
321    Bytes,
322    Font,
323}
324
325impl PartialEq for ResourceType {
326    fn eq(&self, other: &Self) -> bool {
327        match (self, other) {
328            (Self::Bytes, Self::Bytes) => true,
329            (Self::Font, Self::Font) => true,
330            (Self::Shader(option_1), Self::Shader(option_2)) => {
331                option_1.check_has() == option_2.check_has()
332            }
333            (Self::Image(s1, s2), Self::Image(s3, s4)) => s1 == s3 && s2 == s4,
334            _ => false,
335        }
336    }
337}
338
339/// This enum is used to control how resources are loaded.
340/// Background loading will load the resource on another thread
341/// and the resource will be ready when its ready. Blocking Loads
342/// on native platforms will read directly from the filesystem and
343/// the resource will be availble immediately. However on WASM
344/// the blocking behavoir is faked by pausing the game loop
345/// untill all blocking resources are done loading.
346#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
347pub enum LoadingOp {
348    Background,
349    Blocking,
350}
351
352pub(crate) fn generate_id<T>() -> ResourceId<T> {
353    static NEXT_ID: AtomicU64 = AtomicU64::new(1);
354    let id = NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
355    ResourceId(NonZeroU64::new(id).unwrap(), PhantomData::<T>)
356}
357
358/// An Id for a specific type of resource used interally in the engine
359#[derive(PartialOrd, Ord)]
360pub struct ResourceId<T>(NonZeroU64, std::marker::PhantomData<T>);
361
362impl<T> ResourceId<T> {
363    pub(crate) fn from_number(number: NonZeroU64) -> Self {
364        Self(number, PhantomData)
365    }
366
367    pub(crate) fn get_id(&self) -> NonZeroU64 {
368        self.0
369    }
370}
371
372impl<T> Clone for ResourceId<T> {
373    fn clone(&self) -> Self {
374        *self
375    }
376}
377
378impl<T> Copy for ResourceId<T> {}
379
380impl<T> std::fmt::Debug for ResourceId<T> {
381    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382        f.debug_tuple("ID").field(&self.0).finish()
383    }
384}
385
386impl<T> PartialEq for ResourceId<T> {
387    fn eq(&self, other: &Self) -> bool {
388        self.0 == other.0
389    }
390}
391
392impl<T> Eq for ResourceId<T> {}
393
394impl<T> std::hash::Hash for ResourceId<T> {
395    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
396        self.0.hash(state)
397    }
398}
399
400type ResourceMap<T> = HashMap<ResourceId<T>, T>;
401
402pub(crate) struct ResourceManager {
403    btye_resources: ResourceMap<Vec<u8>>,
404    bindgroup_resources: ResourceMap<Texture>,
405    pipeline_resource: ResourceMap<Shader>,
406    fonts: ResourceMap<Font>,
407}
408
409impl ResourceManager {
410    pub fn new() -> Self {
411        Self {
412            btye_resources: HashMap::new(),
413            bindgroup_resources: HashMap::new(),
414            pipeline_resource: HashMap::new(),
415            fonts: HashMap::new(),
416        }
417    }
418
419    pub fn insert_bytes(&mut self, key: ResourceId<Vec<u8>>, data: Vec<u8>) {
420        self.btye_resources.insert(key, data);
421    }
422
423    pub fn insert_texture(&mut self, key: ResourceId<Texture>, data: Texture) {
424        self.bindgroup_resources.insert(key, data);
425    }
426
427    pub fn insert_pipeline(&mut self, key: ResourceId<Shader>, data: Shader) {
428        self.pipeline_resource.insert(key, data);
429    }
430
431    pub fn insert_font(&mut self, key: ResourceId<Font>, data: Font) {
432        self.fonts.insert(key, data);
433    }
434
435    pub fn get_byte_resource(&self, key: &ResourceId<Vec<u8>>) -> Option<&Vec<u8>> {
436        self.btye_resources.get(key)
437    }
438
439    pub fn get_texture(&self, key: &ResourceId<Texture>) -> Option<&Texture> {
440        self.bindgroup_resources.get(key)
441    }
442
443    pub fn get_pipeline(&self, key: &ResourceId<Shader>) -> Option<&Shader> {
444        self.pipeline_resource.get(key)
445    }
446
447    pub fn get_font(&self, key: &ResourceId<Font>) -> Option<&Font> {
448        self.fonts.get(key)
449    }
450
451    pub fn get_mut_shader(&mut self, key: &ResourceId<Shader>) -> Option<&mut Shader> {
452        self.pipeline_resource.get_mut(key)
453    }
454}