Skip to content

Commit

Permalink
replace slotmap dependency with reduced local version
Browse files Browse the repository at this point in the history
  • Loading branch information
cyrgani authored and not-fl3 committed Sep 5, 2024
1 parent 90ebc6f commit 2af0393
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 12 deletions.
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
18 changes: 7 additions & 11 deletions src/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -30,13 +26,13 @@ pub(crate) enum TextureHandle {
}

pub(crate) struct TexturesContext {
textures: SlotMap<crate::texture::TextureSlotId, miniquad::TextureId>,
textures: TextureIdSlotMap,
removed: Vec<TextureSlotId>,
}
impl TexturesContext {
pub fn new() -> TexturesContext {
TexturesContext {
textures: SlotMap::with_key(),
textures: TextureIdSlotMap::new(),
removed: Vec::with_capacity(200),
}
}
Expand All @@ -47,7 +43,7 @@ impl TexturesContext {
TextureHandle::Managed(Arc::new(TextureSlotGuarded(self.textures.insert(texture))))
}
pub fn texture(&self, texture: TextureSlotId) -> Option<miniquad::TextureId> {
self.textures.get(texture).copied()
self.textures.get(texture)
}
// fn remove(&mut self, texture: TextureSlotId) {
// self.textures.remove(texture);
Expand All @@ -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);
Expand Down
141 changes: 141 additions & 0 deletions src/texture/slotmap.rs
Original file line number Diff line number Diff line change
@@ -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<Slot>,
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
/// 2<sup>32</sup> - 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<TextureId> {
self.slots
.get(key.idx as usize)
.filter(|slot| slot.version == key.version.get())
.map(|slot| unsafe { slot.u.value })
}
}

0 comments on commit 2af0393

Please sign in to comment.