use bevy::math::DVec2;
use bevy::platform::collections::HashMap;
use bevy::prelude::{Handle, Mesh, Resource};
use symbios_shape::FaceProfile;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ProfileKey {
Rectangle,
Taper(u32),
Triangle(u32),
Trapezoid(u32, u32),
Polygon(Vec<(u32, u32)>),
}
impl ProfileKey {
pub fn from_profile(profile: &FaceProfile) -> Self {
match profile {
FaceProfile::Rectangle => Self::Rectangle,
FaceProfile::Taper(t) => Self::Taper((*t as f32).to_bits()),
FaceProfile::Triangle { peak_offset } => {
Self::Triangle((*peak_offset as f32).to_bits())
}
FaceProfile::Trapezoid {
top_width,
offset_x,
} => Self::Trapezoid((*top_width as f32).to_bits(), (*offset_x as f32).to_bits()),
FaceProfile::Polygon(pts) => Self::Polygon(
pts.iter()
.map(|p: &DVec2| ((p.x as f32).to_bits(), (p.y as f32).to_bits()))
.collect(),
),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MeshCacheKey {
pub profile: ProfileKey,
pub size_x_bits: u32,
pub size_y_bits: u32,
pub size_z_bits: u32,
pub stretch_uvs: bool,
}
#[derive(Resource, Default, Debug)]
pub struct ShapeMeshCache {
entries: HashMap<MeshCacheKey, Handle<Mesh>>,
hits: u64,
misses: u64,
}
impl ShapeMeshCache {
pub fn new() -> Self {
Self::default()
}
pub fn get(&self, key: &MeshCacheKey) -> Option<&Handle<Mesh>> {
self.entries.get(key)
}
pub fn insert(&mut self, key: MeshCacheKey, handle: Handle<Mesh>) -> Option<Handle<Mesh>> {
self.entries.insert(key, handle)
}
pub fn get_or_insert_with<F: FnOnce() -> Handle<Mesh>>(
&mut self,
key: MeshCacheKey,
build: F,
) -> Handle<Mesh> {
if let Some(h) = self.entries.get(&key) {
self.hits += 1;
return h.clone();
}
self.misses += 1;
let handle = build();
self.entries.insert(key, handle.clone());
handle
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn hits(&self) -> u64 {
self.hits
}
pub fn misses(&self) -> u64 {
self.misses
}
pub fn reset_stats(&mut self) {
self.hits = 0;
self.misses = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_round_trips_for_all_profiles() {
let r = ProfileKey::from_profile(&FaceProfile::Rectangle);
let t = ProfileKey::from_profile(&FaceProfile::Taper(0.5));
let tri = ProfileKey::from_profile(&FaceProfile::Triangle { peak_offset: 0.4 });
let trap = ProfileKey::from_profile(&FaceProfile::Trapezoid {
top_width: 0.6,
offset_x: 0.2,
});
let poly = ProfileKey::from_profile(&FaceProfile::Polygon(vec![
DVec2::new(0.0, 0.0),
DVec2::new(1.0, 0.0),
DVec2::new(1.0, 1.0),
]));
assert_ne!(r, t);
assert_ne!(t, tri);
assert_ne!(tri, trap);
assert_ne!(trap, poly);
}
#[test]
fn get_or_insert_increments_counters() {
let mut cache = ShapeMeshCache::new();
let key = MeshCacheKey {
profile: ProfileKey::Rectangle,
size_x_bits: 1.0_f32.to_bits(),
size_y_bits: 2.0_f32.to_bits(),
size_z_bits: 3.0_f32.to_bits(),
stretch_uvs: false,
};
let h1 = cache.get_or_insert_with(key.clone(), Handle::default);
let h2 = cache.get_or_insert_with(key.clone(), Handle::default);
assert_eq!(h1.id(), h2.id(), "same key should yield same handle id");
assert_eq!(cache.misses(), 1);
assert_eq!(cache.hits(), 1);
assert_eq!(cache.len(), 1);
}
#[test]
fn clear_drops_entries_but_keeps_stats() {
let mut cache = ShapeMeshCache::new();
let key = MeshCacheKey {
profile: ProfileKey::Rectangle,
size_x_bits: 0,
size_y_bits: 0,
size_z_bits: 0,
stretch_uvs: false,
};
cache.get_or_insert_with(key, Handle::default);
cache.clear();
assert_eq!(cache.len(), 0);
assert_eq!(cache.misses(), 1);
cache.reset_stats();
assert_eq!(cache.misses(), 0);
}
}