fennel_core/resources/
mod.rs

1use std::{any::Any, cell::Ref, collections::HashMap, path::PathBuf};
2
3use crate::graphics::Graphics;
4
5/// Module containing implementations of [`LoadableResource`] such as [`loadable::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
14unsafe impl Send for ResourceManager {}
15unsafe impl Sync for ResourceManager {}
16
17/// Trait that all loadable assets must implement
18pub trait LoadableResource: Any {
19    /// Load a resource from `path` and return it boxed
20    ///
21    /// # Arguments
22    /// `path`: path to the resoruce file
23    /// `graphics`: current [`Graphics`] instance which holds `texture_creator` and `ttf_context`
24    /// `size`: optional size for any resoruce that needs it
25    ///
26    /// # Errors
27    /// Returns an error if the file cannot be read or parsed
28    fn load(
29        path: PathBuf,
30        graphics: &mut Graphics,
31        size: Option<f32>,
32    ) -> anyhow::Result<Box<dyn LoadableResource>>
33    where
34        Self: Sized;
35
36    /// Eaasy-to-use identifier for the resource
37    fn name(&self) -> String;
38
39    /// Return a mutable slice that the graphics thread can pass to SDL
40    ///
41    /// If the resource does not have a buffer, then it mustn't implement this function
42    fn as_mut_slice(&self) -> Option<&mut [u8]> {
43        None
44    }
45
46    /// Return an immutable slice for read‑only access
47    ///
48    /// If the resource does not have a buffer, then it mustn't implement this function
49    fn as_slice(&self) -> Option<Ref<'_, [u8]>> {
50        None
51    }
52}
53
54/// evil &Box\<dyn LoadableResource> to &T
55#[allow(clippy::borrowed_box)] // i have no idea how can this be done better because here we box a
56// trait
57pub fn as_concrete<T: 'static + LoadableResource>(
58    b: &Box<dyn LoadableResource>,
59) -> anyhow::Result<&T> {
60    let dyn_ref: &dyn LoadableResource = b.as_ref();
61
62    let any_ref = dyn_ref as &dyn Any;
63
64    Ok(any_ref
65        .downcast_ref::<T>()
66        .expect("incorrect concrete type"))
67}
68
69impl ResourceManager {
70    /// Create a new manager with empty `resources` field
71    pub fn new() -> Self {
72        Self {
73            resources: HashMap::new(),
74        }
75    }
76
77    /// Insert a loaded asset into the cache
78    ///
79    /// The asset is stored under the key returned by `asset.name()`
80    pub fn cache_asset(&mut self, asset: Box<dyn LoadableResource>) -> anyhow::Result<()> {
81        self.resources.insert(asset.name(), asset);
82        Ok(())
83    }
84
85    // here i have NO fucking idea should it be `&Box<dyn LoadableResource>` or whatever
86    // self.resources.get returns a reference to the resource, so basically a reference to Box
87    // but afaik Box is a pointer, and for me it feels a bit fucking wrong to uh return a
88    // reference to a pointer >:3 and also clippy is angry at me for doing this
89    #[allow(clippy::borrowed_box)] // same reason as in `as_concrete`
90    pub fn get_asset(&self, name: String) -> anyhow::Result<&Box<dyn LoadableResource>> {
91        let asset = self.resources.get(&name).unwrap();
92        Ok(asset)
93    }
94
95    /// Check if a resource is cached
96    pub fn is_cached(&self, name: String) -> bool {
97        self.resources.contains_key(&name)
98    }
99}
100
101impl Default for ResourceManager {
102    /// `default()` is equivalent to `ResourceManager::new()`.
103    fn default() -> Self {
104        Self::new()
105    }
106}