fennel_engine/resources/
loadable.rs

1use anyhow::bail;
2use image::ImageReader;
3use sdl3::{
4    pixels::PixelFormat,
5    render::Texture
6};
7use std::{
8    cell::{Ref, RefCell},
9    path::PathBuf,
10    rc::Rc,
11};
12
13use crate::{graphics::Graphics, resources::LoadableResource};
14
15/// Simple image asset that stores its file location.
16pub struct Image {
17    /// Filesystem path to the image.
18    pub path: PathBuf,
19    /// Vector of bytes containing the image pixels
20    pub buffer: Rc<RefCell<Vec<u8>>>,
21    /// SDL3 texture for caching
22    pub texture: Rc<Texture<'static>>,
23    /// Image width
24    pub width: u32,
25    /// Image heiht
26    pub height: u32,
27}
28
29/// Simple font asset
30pub struct Font {
31    /// Filesystem path to the font.
32    pub path: PathBuf,
33    /// Font family name
34    pub family_name: String,
35    /// Point size
36    pub size: f32,
37    /// Vector of bytes containing the font data
38    pub buffer: Rc<sdl3::ttf::Font<'static>>,
39}
40
41impl LoadableResource for Image {
42    /// Construct an `Image` from `path` and return it as a boxed trait object.
43    fn load(
44        path: PathBuf,
45        graphics: &mut Graphics,
46        _size: Option<f32>
47    ) -> anyhow::Result<Box<dyn LoadableResource>> {
48        let img = ImageReader::open(&path)?.decode()?;
49        let mut buffer = img.to_rgba8().into_raw();
50        let surface = sdl3::surface::Surface::from_data(
51            &mut buffer,
52            img.width(),
53            img.height(),
54            img.width() * 4,
55            PixelFormat::RGBA32,
56        )?;
57
58        let texture = unsafe {
59            std::mem::transmute::<sdl3::render::Texture<'_>, sdl3::render::Texture<'static>>(
60                graphics.texture_creator.create_texture_from_surface(surface)?,
61            )
62        };
63
64        Ok(Box::new(Self {
65            path,
66            buffer: Rc::new(RefCell::new(buffer)),
67            texture: Rc::new(texture),
68            width: img.width(),
69            height: img.height(),
70        }))
71    }
72
73    fn name(&self) -> String {
74        self.path.to_string_lossy().to_string()
75    }
76
77    fn as_mut_slice(&self) -> Option<&mut [u8]> {
78        let mut mut_ref = self.buffer.borrow_mut();
79        // even more evil shit that PROBABLY :) should be safe because as we know in normal conditions only
80        // one thread should access (graphics, audio, ..) its respecting resources
81        // otherwise have a SEGFAULT >:3
82        unsafe { Some(&mut *(mut_ref.as_mut_slice() as *mut [u8])) }
83    }
84
85    fn as_slice(&self) -> Option<Ref<'_, [u8]>> {
86        Some(Ref::map(self.buffer.borrow(), |v| v.as_slice()))
87    }
88}
89
90impl LoadableResource for Font {
91    fn load(
92            path: PathBuf,
93            graphics: &mut Graphics,
94            size: Option<f32>
95        ) -> anyhow::Result<Box<dyn LoadableResource>>
96        where
97            Self: Sized {
98        if size.is_none() {
99            bail!("no font size was provided");
100        }
101
102        let font = graphics.ttf_context.load_font(&path, size.unwrap())?;
103        Ok(Box::new(Self {
104            path,
105            family_name: font.face_family_name().expect("failed to get font family name"),
106            size: size.unwrap(),
107            buffer: Rc::new(font)
108        }))
109    }
110
111    fn name(&self) -> String {
112        format!("{} {}", self.family_name, self.size)
113    }
114}