diff --git a/Cargo.toml b/Cargo.toml index 842e30d1..b3c36807 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ fontdue = "0.7" backtrace = { version = "0.3.60", optional = true, default-features = false, features = [ "std", "libbacktrace" ] } log = { version = "0.4", optional = true } quad-snd = { version = "0.2", optional = true } -slotmap = "1.0" [dev-dependencies] macroquad-particles = { path = "./particles" } diff --git a/src/texture.rs b/src/texture.rs index 15d87044..e35b7de5 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -5,17 +5,13 @@ use crate::{ text::atlas::SpriteKey, Error, }; +pub use crate::quad_gl::FilterMode; use crate::quad_gl::{DrawMode, Vertex}; use glam::{vec2, Vec2}; - -pub use crate::quad_gl::FilterMode; - -use slotmap::SlotMap; +use slotmap::{TextureIdSlotMap, TextureSlotId}; use std::sync::Arc; -slotmap::new_key_type! { - pub(crate) struct TextureSlotId; -} +mod slotmap; #[derive(Debug, Clone, PartialEq)] pub(crate) struct TextureSlotGuarded(pub TextureSlotId); @@ -30,13 +26,13 @@ pub(crate) enum TextureHandle { } pub(crate) struct TexturesContext { - textures: SlotMap, + textures: TextureIdSlotMap, removed: Vec, } impl TexturesContext { pub fn new() -> TexturesContext { TexturesContext { - textures: SlotMap::with_key(), + textures: TextureIdSlotMap::new(), removed: Vec::with_capacity(200), } } @@ -47,7 +43,7 @@ impl TexturesContext { TextureHandle::Managed(Arc::new(TextureSlotGuarded(self.textures.insert(texture)))) } pub fn texture(&self, texture: TextureSlotId) -> Option { - self.textures.get(texture).copied() + self.textures.get(texture) } // fn remove(&mut self, texture: TextureSlotId) { // self.textures.remove(texture); @@ -57,7 +53,7 @@ impl TexturesContext { } pub fn garbage_collect(&mut self, ctx: &mut miniquad::Context) { for texture in self.removed.drain(0..) { - if let Some(texture) = self.textures.get(texture).copied() { + if let Some(texture) = self.textures.get(texture) { ctx.delete_texture(texture); } self.textures.remove(texture); diff --git a/src/texture/slotmap.rs b/src/texture/slotmap.rs new file mode 100644 index 00000000..3476f3f3 --- /dev/null +++ b/src/texture/slotmap.rs @@ -0,0 +1,141 @@ +// Heavily reduced version of the `slotmap` crate: https://github.com/orlp/slotmap +use miniquad::TextureId; +use std::fmt; +use std::num::NonZeroU32; + +#[derive(Copy, Clone, PartialEq)] +pub(crate) struct TextureSlotId { + idx: u32, + version: NonZeroU32, +} + +impl fmt::Debug for TextureSlotId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}v{}", self.idx, self.version.get()) + } +} + +impl TextureSlotId { + fn new(idx: u32, version: u32) -> Self { + debug_assert!(version > 0); + + Self { + idx, + version: unsafe { NonZeroU32::new_unchecked(version | 1) }, + } + } +} + +// Storage inside a slot or metadata for the freelist when vacant. +union SlotUnion { + value: TextureId, + next_free: u32, +} + +// A slot, which represents storage for a value and a current version. +// Can be occupied or vacant. +struct Slot { + u: SlotUnion, + version: u32, // Even = vacant, odd = occupied. +} + +/// Slot map, storage with stable unique keys. +pub(crate) struct TextureIdSlotMap { + slots: Vec, + free_head: u32, + num_elems: u32, +} + +impl TextureIdSlotMap { + /// Constructs a new, empty [`TextureIdSlotMap`]. + pub fn new() -> Self { + let slots = vec![Slot { + u: SlotUnion { next_free: 0 }, + version: 0, + }]; + + Self { + slots, + free_head: 1, + num_elems: 0, + } + } + + /// Returns the number of elements in the slot map. + pub fn len(&self) -> usize { + self.num_elems as usize + } + + /// Returns [`true`] if the slot map contains `key`. + #[inline(always)] + fn contains_key(&self, key: TextureSlotId) -> bool { + self.slots + .get(key.idx as usize) + .map_or(false, |slot| slot.version == key.version.get()) + } + + /// Inserts a value into the slot map. Returns a unique key that can be used + /// to access this value. + /// + /// # Panics + /// + /// Panics if the number of elements in the slot map equals + /// 232 - 2. + pub fn insert(&mut self, value: TextureId) -> TextureSlotId { + let new_num_elems = self.num_elems + 1; + if new_num_elems == u32::MAX { + panic!("SlotMap number of elements overflow"); + } + + if let Some(slot) = self.slots.get_mut(self.free_head as usize) { + let occupied_version = slot.version | 1; + let kd = TextureSlotId::new(self.free_head, occupied_version); + + // Update. + unsafe { + self.free_head = slot.u.next_free; + slot.u.value = value; + slot.version = occupied_version; + } + self.num_elems = new_num_elems; + return kd; + } + + let version = 1; + let kd = TextureSlotId::new(self.slots.len() as u32, version); + + // Create new slot before adjusting freelist in case f or the allocation panics or errors. + self.slots.push(Slot { + u: SlotUnion { value }, + version, + }); + + self.free_head = kd.idx + 1; + self.num_elems = new_num_elems; + kd + } + + /// Removes a key from the slot map if it is present. + pub fn remove(&mut self, key: TextureSlotId) { + if self.contains_key(key) { + let idx = key.idx as usize; + + // This is safe because we know that the slot is occupied. + let slot = unsafe { self.slots.get_unchecked_mut(idx) }; + + // Maintain freelist. + slot.u.next_free = self.free_head; + self.free_head = idx as u32; + self.num_elems -= 1; + slot.version = slot.version.wrapping_add(1); + } + } + + /// Returns a reference to the value corresponding to the key. + pub fn get(&self, key: TextureSlotId) -> Option { + self.slots + .get(key.idx as usize) + .filter(|slot| slot.version == key.version.get()) + .map(|slot| unsafe { slot.u.value }) + } +}