use alloc::collections::BTreeSet;
use alloc::vec;
use alloc::vec::Vec;
use crate::ecs::{PayloadLocator, ResourceKind, ResourceRecord};
#[derive(Debug, Clone, Default)]
pub struct ResourceEntry {
pub payload: Option<PayloadLocator>,
pub data_bytes: Vec<u8>,
}
pub(crate) fn resource_table(
records: &mut [ResourceRecord],
kind: ResourceKind,
) -> Vec<ResourceEntry> {
let tag = kind as u8;
let Some(max_handle) = records
.iter()
.filter(|r| r.resource_kind == tag)
.map(|r| r.handle)
.max()
else {
return Vec::new();
};
let mut table = vec![ResourceEntry::default(); max_handle as usize + 1];
for record in records.iter_mut().filter(|r| r.resource_kind == tag) {
table[record.handle as usize] = ResourceEntry {
payload: record.payload.clone(),
data_bytes: core::mem::take(&mut record.data_bytes),
};
}
table
}
macro_rules! resource_tables {
($($name:ident => $kind:ident),* $(,)?) => {
$(
#[derive(Debug, Clone, Default)]
pub struct $name(pub Vec<ResourceEntry>);
impl $name {
pub fn from_records(records: &mut [ResourceRecord]) -> Self {
Self(resource_table(records, ResourceKind::$kind))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn locator(&self, handle: usize) -> Option<PayloadLocator> {
self.0.get(handle).and_then(|e| e.payload.clone())
}
pub fn locators(&self) -> impl Iterator<Item = (usize, PayloadLocator)> + '_ {
self.0
.iter()
.enumerate()
.filter_map(|(i, e)| e.payload.clone().map(|l| (i, l)))
}
pub fn blob_indices(&self) -> BTreeSet<u32> {
self.0
.iter()
.filter_map(|e| e.payload.as_ref().map(|l| l.blob_index))
.collect()
}
}
)*
};
}
resource_tables! {
AudioClipTable => AudioClip,
TextureTable => Texture,
ColorLutTable => ColorLut,
EnvironmentMapTable => EnvironmentMap,
FontTable => Font,
MeshTable => Mesh,
SkinnedMeshTable => SkinnedMesh,
MaterialTable => Material,
}
impl SkinnedMeshTable {
pub fn has_capsule(&self) -> bool {
self.0.iter().any(|e| {
crate::blob::decode_exact::<(u32, crate::components::SkinnedMesh)>(&e.data_bytes)
.is_ok_and(|(_, sm)| sm.capsule.is_some())
})
}
}
impl MaterialTable {
pub fn data_bytes(&self, handle: usize) -> Option<&[u8]> {
self.0.get(handle).map(|e| e.data_bytes.as_slice())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rec(kind: ResourceKind, handle: u32, blob_index: u32) -> ResourceRecord {
ResourceRecord {
resource_kind: kind as u8,
handle,
payload: Some(PayloadLocator {
blob_index,
offset: 0,
len: 1,
}),
data_bytes: Vec::new(),
}
}
#[test]
fn table_is_dense_by_handle_and_ignores_other_kinds() {
let mut records = vec![
rec(ResourceKind::AudioClip, 1, 0),
rec(ResourceKind::Mesh, 0, 5),
rec(ResourceKind::AudioClip, 0, 0),
];
let table = AudioClipTable::from_records(&mut records);
assert_eq!(table.0.len(), 2);
assert!(table.locator(0).is_some());
assert!(table.locator(1).is_some());
assert!(table.locator(2).is_none());
}
#[test]
fn empty_stream_yields_an_empty_table() {
let table = AudioClipTable::from_records(&mut []);
assert!(table.0.is_empty());
assert!(table.blob_indices().is_empty());
assert_eq!(table.locators().count(), 0);
}
#[test]
fn blob_indices_collects_every_payload_blob() {
let mut records = vec![
rec(ResourceKind::AudioClip, 0, 0),
rec(ResourceKind::AudioClip, 1, 3),
];
let table = AudioClipTable::from_records(&mut records);
let mut indices: Vec<u32> = table.blob_indices().into_iter().collect();
indices.sort_unstable();
assert_eq!(indices, vec![0, 3]);
}
#[test]
fn texture_table_is_dense_by_handle_and_ignores_other_kinds() {
let mut records = vec![
rec(ResourceKind::Texture, 1, 2),
rec(ResourceKind::AudioClip, 0, 9),
rec(ResourceKind::Texture, 0, 1),
];
let table = TextureTable::from_records(&mut records);
assert_eq!(table.len(), 2);
assert!(table.locator(0).is_some());
assert!(table.locator(1).is_some());
assert!(table.locator(2).is_none());
let mut indices: Vec<u32> = table.blob_indices().into_iter().collect();
indices.sort_unstable();
assert_eq!(indices, vec![1, 2]);
assert!(TextureTable::from_records(&mut []).is_empty());
}
#[test]
fn every_table_reads_only_its_own_kind() {
let mut records = vec![
rec(ResourceKind::AudioClip, 0, 0),
rec(ResourceKind::Texture, 0, 1),
rec(ResourceKind::ColorLut, 0, 2),
rec(ResourceKind::EnvironmentMap, 0, 3),
rec(ResourceKind::Font, 0, 4),
rec(ResourceKind::Mesh, 0, 5),
rec(ResourceKind::SkinnedMesh, 0, 6),
rec(ResourceKind::Material, 0, 7),
];
assert_eq!(AudioClipTable::from_records(&mut records).len(), 1);
assert_eq!(TextureTable::from_records(&mut records).len(), 1);
assert_eq!(ColorLutTable::from_records(&mut records).len(), 1);
assert_eq!(EnvironmentMapTable::from_records(&mut records).len(), 1);
assert_eq!(FontTable::from_records(&mut records).len(), 1);
assert_eq!(MeshTable::from_records(&mut records).len(), 1);
assert_eq!(SkinnedMeshTable::from_records(&mut records).len(), 1);
assert_eq!(MaterialTable::from_records(&mut records).len(), 1);
}
}