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