summaryrefslogtreecommitdiff
path: root/resources/src
diff options
context:
space:
mode:
Diffstat (limited to 'resources/src')
-rw-r--r--resources/src/lib.rs13
-rw-r--r--resources/src/texture.rs394
2 files changed, 0 insertions, 407 deletions
diff --git a/resources/src/lib.rs b/resources/src/lib.rs
deleted file mode 100644
index 9cbbba0..0000000
--- a/resources/src/lib.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-#![feature(new_uninit, let_chains)]
-#![warn(clippy::nursery, clippy::pedantic)]
-#![allow(clippy::module_name_repetitions)]
-
-pub mod texture;
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
-pub enum Priority {
- Unnecessary,
- Possible(u8),
- Eventual(u8),
- Urgent,
-}
diff --git a/resources/src/texture.rs b/resources/src/texture.rs
deleted file mode 100644
index 3a5bf3e..0000000
--- a/resources/src/texture.rs
+++ /dev/null
@@ -1,394 +0,0 @@
-use std::cmp::Reverse;
-use std::mem;
-use std::path::Path;
-use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
-use std::sync::Arc;
-
-use dashmap::DashMap;
-use image::ImageBuffer;
-use parking_lot::Mutex;
-use thiserror::Error;
-
-use crate::Priority;
-
-/// The next texture ID
-static NEXT_TEXTURE_ID: AtomicUsize = AtomicUsize::new(0);
-
-/// A unique identifier for a texture
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-pub struct TextureId(usize);
-
-impl TextureId {
- fn new() -> Self {
- Self(NEXT_TEXTURE_ID.fetch_add(1, Ordering::Relaxed))
- }
-}
-
-/// These are the formats supported by the renderer.
-// TODO make these feature-enabled
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
-#[non_exhaustive]
-pub enum ImageFormat {
- Bmp,
- Ico,
- Farbfeld,
-}
-
-impl From<ImageFormat> for image::ImageFormat {
- fn from(format: ImageFormat) -> Self {
- match format {
- ImageFormat::Bmp => Self::Bmp,
- ImageFormat::Ico => Self::Ico,
- ImageFormat::Farbfeld => Self::Farbfeld,
- }
- }
-}
-
-#[derive(Debug, Error)]
-#[error("{}", .0)]
-pub struct DecodingError(#[from] image::error::DecodingError);
-
-#[allow(clippy::missing_const_for_fn)]
-fn convert_image_decoding(e: image::ImageError) -> DecodingError {
- if let image::ImageError::Decoding(de) = e {
- de.into()
- } else {
- unreachable!("No other error should be possible")
- }
-}
-
-#[derive(Debug, Error)]
-pub enum LoadError {
- #[error("{}", .0)]
- Decoding(#[from] DecodingError),
- #[error("{}", .0)]
- Io(#[from] std::io::Error),
-}
-
-fn convert_image_load_error(e: image::ImageError) -> LoadError {
- match e {
- image::ImageError::Decoding(de) => LoadError::Decoding(de.into()),
- image::ImageError::IoError(ioe) => ioe.into(),
- _ => unreachable!("No other error should be possible"),
- }
-}
-
-pub type Rgba16Texture = image::ImageBuffer<image::Rgba<u16>, Box<[u16]>>;
-
-fn vec_image_to_box(vec_image: image::ImageBuffer<image::Rgba<u16>, Vec<u16>>) -> Rgba16Texture {
- let width = vec_image.width();
- let height = vec_image.height();
- let buf = vec_image.into_raw().into_boxed_slice();
- ImageBuffer::from_raw(width, height, buf).expect("image buffer is too small")
-}
-
-/// Get the size, in bytes, of the texture
-#[allow(clippy::missing_const_for_fn)]
-fn texture_size(image: &Rgba16Texture) -> usize {
- image.len() * mem::size_of::<image::Rgba<u16>>()
-}
-
-/// A texture from disk
-struct TextureFile {
- path: Box<Path>,
- texture: Option<Arc<Rgba16Texture>>,
-}
-
-impl TextureFile {
- /// This doesn't load the texture
- #[allow(clippy::missing_const_for_fn)]
- fn new(path: impl AsRef<Path>) -> Self {
- Self {
- path: path.as_ref().into(),
- texture: None,
- }
- }
-
- const fn is_loaded(&self) -> bool {
- self.texture.is_some()
- }
-
- fn load(&mut self) -> Result<&Rgba16Texture, LoadError> {
- if self.texture.is_none() {
- log::warn!("{} was not pre-loaded", self.path.to_string_lossy());
- let texture = image::open(&self.path).map_err(convert_image_load_error)?;
- let texture = texture.to_rgba16();
- let texture = Arc::new(vec_image_to_box(texture));
- self.texture = Some(texture);
- }
-
- Ok(self.texture.as_ref().expect("the texture wasn't loaded"))
- }
-
- fn loaded_texture(&self) -> Option<&Rgba16Texture> {
- self.texture.as_deref()
- }
-
- fn is_used(&self) -> bool {
- let Some(arc) = &self.texture else { return false };
- Arc::strong_count(arc) > 1
- }
-
- /// Unloads the texture from memory if it isn't being used
- fn unload(&mut self) {
- if !self.is_used() {
- self.texture = None;
- }
- }
-
- /// The amount of heap memory used, in bytes. This returns 0 if the texture
- /// hasn't been loaded yet.
- fn allocated_size(&self) -> usize {
- self.texture.as_ref().map_or(0, |t| texture_size(t))
- }
-}
-
-enum TextureBuffer {
- Memory(Arc<Rgba16Texture>),
- Disk(TextureFile),
-}
-
-struct Texture {
- priority: Priority,
- queued_priority: Arc<Mutex<Option<Priority>>>,
- buffer: TextureBuffer,
-}
-
-impl Texture {
- fn from_buffer(texture: Rgba16Texture) -> Self {
- Self {
- priority: Priority::Urgent, // indicates that it can't be unloaded
- queued_priority: Arc::new(Mutex::new(None)),
- buffer: TextureBuffer::Memory(Arc::new(texture)),
- }
- }
-
- fn from_path(path: impl AsRef<Path>, priority: Priority) -> Self {
- Self {
- priority,
- queued_priority: Arc::new(Mutex::new(None)),
- buffer: TextureBuffer::Disk(TextureFile::new(path)),
- }
- }
-
- const fn priority(&self) -> Priority {
- self.priority
- }
-
- fn _set_priority(buffer: &TextureBuffer, src: &mut Priority, priority: Priority) -> bool {
- // memory textures and textures in use should always be urgent
- if let TextureBuffer::Disk(disk) = buffer && !disk.is_used() {
- *src = priority;
- true
- } else {
- false
- }
- }
-
- fn unqueue_priority(&mut self) {
- let mut queued_priority = self.queued_priority.lock();
- let unqueued_priority = queued_priority.unwrap_or(Priority::Unnecessary);
-
- if Self::_set_priority(&self.buffer, &mut self.priority, unqueued_priority) {
- *queued_priority = None;
- }
- }
-
- fn set_priority(&mut self, priority: Priority) {
- Self::_set_priority(&self.buffer, &mut self.priority, priority);
- }
-
- fn load_texture(&mut self) -> Result<&Rgba16Texture, LoadError> {
- match &mut self.buffer {
- TextureBuffer::Memory(ref texture) => Ok(texture),
- TextureBuffer::Disk(file) => file.load(),
- }
- }
-
- /// If the texture is loaded, return it.
- fn loaded_texture(&self) -> Option<&Rgba16Texture> {
- match &self.buffer {
- TextureBuffer::Memory(ref texture) => Some(texture),
- TextureBuffer::Disk(file) => file.loaded_texture(),
- }
- }
-
- fn unload(&mut self) {
- if let TextureBuffer::Disk(file) = &mut self.buffer {
- file.unload();
- }
- }
-
- /// The amount of heap memory used for the texture, if any
- fn allocated_size(&self) -> usize {
- match &self.buffer {
- TextureBuffer::Memory(texture) => texture_size(texture),
- TextureBuffer::Disk(file) => file.allocated_size(),
- }
- }
-
- const fn is_loaded(&self) -> bool {
- match &self.buffer {
- TextureBuffer::Memory(_) => true,
- TextureBuffer::Disk(file) => file.is_loaded(),
- }
- }
-}
-
-pub struct TextureManager {
- textures: DashMap<TextureId, Texture>,
- max_size: usize,
- needs_atlas_update: AtomicBool,
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct TextureManagerConfig {
- /// The initial capacity of the texture manager. This defaults to 500 textures.
- pub initial_capacity: usize,
- /// The maximum amount of heap usage acceptable. Defaults to 10 MiB.
- pub max_size: usize,
-}
-
-impl Default for TextureManagerConfig {
- fn default() -> Self {
- Self {
- initial_capacity: 500,
- max_size: 10 * 1024 * 1024, // 10 MiB
- }
- }
-}
-
-impl TextureManager {
- /// Create a new `TextureManager` with the given config options.
- #[must_use]
- pub fn new(config: &TextureManagerConfig) -> Self {
- let textures = DashMap::with_capacity(config.initial_capacity);
-
- Self {
- textures,
- max_size: config.max_size,
- needs_atlas_update: AtomicBool::new(false),
- }
- }
-
- /// Load textures into memory that will be needed soon. Unload unnecessary textures
- pub fn cache_files(&self) {
- let mut textures: Vec<_> = self
- .textures
- .iter_mut()
- .map(|mut t| {
- t.value_mut().unqueue_priority();
- t
- })
- .collect();
- textures.sort_by_key(|t2| Reverse(t2.priority()));
-
- let max_size = self.max_size;
- let mut total_size = 0;
-
- for texture in &mut textures {
- drop(texture.load_texture());
- total_size += texture.allocated_size();
- if total_size > max_size && texture.priority() != Priority::Urgent {
- texture.unload();
- return;
- }
- }
- }
-
- /// Loads a texture from memory in the given format.
- ///
- /// # Errors
- ///
- /// This returns `Expected(DecodingError)` if the given buffer was invalid
- /// for the given format.
- pub fn load_from_memory(
- &self,
- buf: &[u8],
- format: ImageFormat,
- ) -> Result<TextureId, DecodingError> {
- let id = TextureId::new();
- let texture = image::load_from_memory_with_format(buf, format.into());
- let texture = texture.map_err(convert_image_decoding)?;
- let texture = texture.into_rgba16();
- let texture = vec_image_to_box(texture);
- let texture = Texture::from_buffer(texture);
-
- self.textures.insert(id, texture);
- self.needs_atlas_update.store(true, Ordering::Release);
-
- Ok(id)
- }
-
- /// Loads a texture from disk.
- ///
- /// # Errors
- ///
- /// This returns an error if `priority` is set to [`Priority::Urgent`] but
- /// there was an error in loading the file to a texture.
- pub fn load_from_file(
- &self,
- path: impl AsRef<Path>,
- priority: Priority,
- ) -> Result<TextureId, LoadError> {
- let id = TextureId::new();
- let mut texture = Texture::from_path(path, priority);
-
- if priority == Priority::Urgent {
- match texture.load_texture() {
- Ok(_) => {
- self.textures.insert(id, texture);
- self.needs_atlas_update.store(true, Ordering::Release);
- }
- Err(e) => {
- self.textures.insert(id, texture);
- return Err(e);
- }
- }
- } else {
- self.textures.insert(id, texture);
- }
-
- Ok(id)
- }
-
- /// Loads a texture from disk.
- ///
- /// # Errors
- ///
- /// This returns an error if `priority` is set to [`Priority::Urgent`] but
- /// there was an error in loading the file to a texture.
- pub fn set_priority(&self, id: TextureId, priority: Priority) -> Result<(), LoadError> {
- let mut texture = self.textures.get_mut(&id).expect("invalid texture id");
- texture.set_priority(priority);
-
- if !texture.is_loaded() && priority == Priority::Urgent {
- let mut texture = self.textures.get_mut(&id).expect("invalid texture id");
- texture.load_texture()?;
- self.needs_atlas_update.store(true, Ordering::Release);
- }
-
- Ok(())
- }
-
- /// This returns `true` if a texture has been set to have an urgent
- /// priority since the last time this function was called.
- pub fn needs_atlas_update(&self) -> bool {
- self.needs_atlas_update.fetch_and(false, Ordering::AcqRel)
- }
-
- /// Load a texture into memory, if it hasn't been already. Then return a
- /// copy of the texture.
- ///
- /// # Errors
- ///
- /// This returns an error if an error occurs in loading the texture from
- /// disk, such as the file not existing, or not being a valid texture.
- pub fn load_texture(&self, id: TextureId) -> Result<Rgba16Texture, LoadError> {
- self.textures
- .get_mut(&id)
- .expect("the TextureId was invalid")
- .load_texture()
- .cloned()
- }
-}