fennel_engine/resources/
mod.rs

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