fennel_engine/resources/
mod.rs

1use std::{any::Any, cell::Ref, collections::HashMap, path::PathBuf};
2
3/// Module containing implementations of [`LoadableResource`] such as [`Image`]
4pub mod loadable;
5
6/// Manages a collection of loadable resources indexed by their name
7pub struct ResourceManager {
8    /// Map resource name to a type that implements [`LoadableResource`] trait
9    pub resources: HashMap<String, Box<dyn LoadableResource>>,
10}
11
12/// Trait that all loadable assets must implement
13pub trait LoadableResource: Any {
14    /// Load a resource from `path` and return it boxed
15    ///
16    /// # Errors
17    /// Returns an error if the file cannot be read or parsed
18    fn load(path: PathBuf) -> anyhow::Result<Box<dyn LoadableResource>>
19    where
20        Self: Sized;
21
22    /// Eaasy-to-use identifier for the resource
23    fn name(&self) -> String;
24
25    /// Return a mutable slice that the graphics thread can pass to SDL
26    fn as_mut_slice(&self) -> &mut [u8];
27
28    /// Return an immutable slice for read‑only access
29    fn as_slice(&self) -> Ref<'_, [u8]>;
30}
31
32/// evil &Box<dyn LoadableResource> to &T
33pub fn as_concrete<T: 'static + LoadableResource>(b: &Box<dyn LoadableResource>) -> &T {
34    let dyn_ref: &dyn LoadableResource = b.as_ref();
35
36    let any_ref = dyn_ref as &dyn Any;
37
38    any_ref
39        .downcast_ref::<T>()
40        .expect("incorrect concrete type")
41}
42
43impl ResourceManager {
44    /// Create a new manager with empty `resources` field
45    pub fn new() -> Self {
46        Self {
47            resources: HashMap::new(),
48        }
49    }
50
51    /// Insert a loaded asset into the cache
52    ///
53    /// The asset is stored under the key returned by `asset.name()`
54    pub fn cache_asset(&mut self, asset: Box<dyn LoadableResource>) -> anyhow::Result<()> {
55        self.resources.insert(asset.name(), asset);
56        Ok(())
57    }
58
59    // here i have NO fucking idea should it be `&Box<dyn LoadableResource>` or whatever
60    // self.resources.get returns a reference to the resource, so basically a reference to Box
61    // but afaik Box is a pointer, and for me it feels a bit fucking wrong to uh return a
62    // reference to a pointer >:3 and also clippy is angry at me for doing this
63    pub fn get_asset(&mut self, name: String) -> anyhow::Result<&Box<dyn LoadableResource>> {
64        let asset = self.resources.get(&name).unwrap();
65        Ok(asset)
66    }
67
68    pub fn is_cached(&mut self, name: String) -> bool {
69        self.resources.contains_key(&name)
70    }
71}
72
73impl Default for ResourceManager {
74    /// `default()` is equivalent to `ResourceManager::new()`.
75    fn default() -> Self {
76        Self::new()
77    }
78}