269 lines
7.7 KiB
Rust
269 lines
7.7 KiB
Rust
use super::Batch;
|
|
use smallvec::{smallvec, SmallVec};
|
|
use std::{borrow::Cow, collections::HashMap, hash::Hash};
|
|
|
|
// TODO: add sorting by primary / secondary handle to reduce rebinds of data
|
|
|
|
// TValue: entityid
|
|
// TKey: handleuntyped
|
|
|
|
pub trait Key: Clone + Eq + Hash + 'static {}
|
|
impl<T: Clone + Eq + Hash + 'static> Key for T {}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
|
pub struct BatchKey<TKey: Key>(pub Cow<'static, SmallVec<[TKey; 2]>>);
|
|
|
|
impl<TKey: Key> BatchKey<TKey> {
|
|
pub fn key1(key: TKey) -> Self {
|
|
BatchKey(Cow::Owned(smallvec![key]))
|
|
}
|
|
|
|
pub fn key2(key1: TKey, key2: TKey) -> Self {
|
|
BatchKey(Cow::Owned(smallvec![key1, key2]))
|
|
}
|
|
|
|
pub fn key3(key1: TKey, key2: TKey, key3: TKey) -> Self {
|
|
BatchKey(Cow::Owned(smallvec![key1, key2, key3]))
|
|
}
|
|
}
|
|
|
|
pub struct BatcherKeyState<TKey: Key> {
|
|
batch_key: Option<BatchKey<TKey>>,
|
|
keys: SmallVec<[Option<TKey>; 2]>,
|
|
}
|
|
|
|
impl<TKey: Key> BatcherKeyState<TKey> {
|
|
pub fn new(size: usize) -> Self {
|
|
BatcherKeyState {
|
|
keys: smallvec![None; size],
|
|
batch_key: None,
|
|
}
|
|
}
|
|
|
|
pub fn set(&mut self, index: usize, key: TKey) {
|
|
self.keys[index] = Some(key);
|
|
}
|
|
|
|
pub fn finish(&mut self) -> Option<BatchKey<TKey>> {
|
|
let finished = self.keys.iter().filter(|x| x.is_some()).count() == self.keys.len();
|
|
if finished {
|
|
let batch_key = BatchKey(Cow::Owned(
|
|
self.keys
|
|
.drain(..)
|
|
.map(|k| k.unwrap())
|
|
.collect::<SmallVec<[TKey; 2]>>(),
|
|
));
|
|
self.batch_key = Some(batch_key);
|
|
self.batch_key.clone()
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An unordered batcher intended to support an arbitrary number of keys of the same type (but with some distinguishing factor)
|
|
/// NOTE: this may or may not be useful for anything. when paired with a higher-level "BatcherSet" it would allow updating batches
|
|
// per-key (ex: material, mesh) with no global knowledge of the number of batch types (ex: (Mesh), (Material, Mesh)) that key belongs
|
|
// to. The downside is that it is completely unordered, so it probably isn't useful for front->back or back->front rendering. But
|
|
// _maybe_ for gpu instancing?
|
|
pub struct Batcher<TKey, TValue, TData>
|
|
where
|
|
TKey: Key,
|
|
{
|
|
pub batches: HashMap<BatchKey<TKey>, Batch<TKey, TValue, TData>>,
|
|
pub is_index: Vec<fn(&TKey) -> bool>,
|
|
pub key_states: HashMap<TValue, BatcherKeyState<TKey>>,
|
|
pub key_count: usize,
|
|
}
|
|
|
|
impl<TKey, TValue, TData> Batcher<TKey, TValue, TData>
|
|
where
|
|
TKey: Key,
|
|
TValue: Clone + Eq + Hash,
|
|
TData: Default,
|
|
{
|
|
pub fn new(is_index: Vec<fn(&TKey) -> bool>) -> Self {
|
|
Batcher {
|
|
batches: HashMap::default(),
|
|
key_states: HashMap::default(),
|
|
key_count: is_index.len(),
|
|
is_index,
|
|
}
|
|
}
|
|
|
|
pub fn get_batch(&self, batch_key: &BatchKey<TKey>) -> Option<&Batch<TKey, TValue, TData>> {
|
|
self.batches.get(batch_key)
|
|
}
|
|
|
|
pub fn get_batch_mut(
|
|
&mut self,
|
|
batch_key: &BatchKey<TKey>,
|
|
) -> Option<&mut Batch<TKey, TValue, TData>> {
|
|
self.batches.get_mut(batch_key)
|
|
}
|
|
|
|
pub fn add(&mut self, key: TKey, value: TValue) -> bool {
|
|
let batch_key = {
|
|
let key_count = self.key_count;
|
|
let key_state = self
|
|
.key_states
|
|
.entry(value.clone())
|
|
.or_insert_with(|| BatcherKeyState::new(key_count));
|
|
|
|
// if all key states are set, the value is already in the batch
|
|
if key_state.batch_key.is_some() {
|
|
// TODO: if weights are ever added, make sure to get the batch and set the weight here
|
|
return true;
|
|
}
|
|
|
|
let key_index = self
|
|
.is_index
|
|
.iter()
|
|
.enumerate()
|
|
.find(|(_i, is_index)| is_index(&key))
|
|
.map(|(i, _)| i);
|
|
if let Some(key_index) = key_index {
|
|
key_state.set(key_index, key.clone());
|
|
key_state.finish()
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if let Some(batch_key) = batch_key {
|
|
let batch = self
|
|
.batches
|
|
.entry(batch_key.clone())
|
|
.or_insert_with(|| Batch::new(batch_key, TData::default()));
|
|
|
|
batch.add(value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Batch<TKey, TValue, TData>> {
|
|
self.batches.values()
|
|
}
|
|
|
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Batch<TKey, TValue, TData>> {
|
|
self.batches.values_mut()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{Batch, BatchKey, Batcher};
|
|
use bevy_asset::{Handle, HandleUntyped};
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct A;
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct B;
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
struct C;
|
|
#[derive(Debug, Eq, PartialEq, Default)]
|
|
struct Data;
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
struct Entity(usize);
|
|
#[test]
|
|
fn test_batcher_2() {
|
|
let mut batcher: Batcher<HandleUntyped, Entity, Data> = Batcher::new(vec![
|
|
HandleUntyped::is_handle::<A>,
|
|
HandleUntyped::is_handle::<B>,
|
|
]);
|
|
|
|
let e1 = Entity(1);
|
|
let e2 = Entity(2);
|
|
let e3 = Entity(3);
|
|
|
|
let a1: HandleUntyped = Handle::<A>::new().into();
|
|
let b1: HandleUntyped = Handle::<B>::new().into();
|
|
let c1: HandleUntyped = Handle::<C>::new().into();
|
|
|
|
let a2: HandleUntyped = Handle::<A>::new().into();
|
|
let b2: HandleUntyped = Handle::<B>::new().into();
|
|
|
|
let a1_b1 = BatchKey::key2(a1, b1);
|
|
let a2_b2 = BatchKey::key2(a2, b2);
|
|
|
|
assert_eq!(
|
|
batcher.get_batch(&a1_b1),
|
|
None,
|
|
"a1_b1 batch should not exist yet"
|
|
);
|
|
batcher.add(a1, e1);
|
|
assert_eq!(
|
|
batcher.get_batch(&a1_b1),
|
|
None,
|
|
"a1_b1 batch should not exist yet"
|
|
);
|
|
batcher.add(b1, e1);
|
|
|
|
let a1_b1_batch = Batch {
|
|
batch_key: a1_b1.clone(),
|
|
values: vec![e1],
|
|
data: Data,
|
|
};
|
|
|
|
assert_eq!(
|
|
batcher.get_batch(&a1_b1),
|
|
Some(&a1_b1_batch),
|
|
"a1_b1 batch should exist"
|
|
);
|
|
|
|
assert_eq!(
|
|
batcher.get_batch(&a2_b2),
|
|
None,
|
|
"a2_b2 batch should not exist yet"
|
|
);
|
|
batcher.add(a2, e2);
|
|
assert_eq!(
|
|
batcher.get_batch(&a2_b2),
|
|
None,
|
|
"a2_b2 batch should not exist yet"
|
|
);
|
|
batcher.add(b2, e2);
|
|
|
|
let expected_batch = Batch {
|
|
batch_key: a2_b2.clone(),
|
|
values: vec![e2],
|
|
data: Data,
|
|
};
|
|
|
|
assert_eq!(
|
|
batcher.get_batch(&a2_b2),
|
|
Some(&expected_batch),
|
|
"a2_b2 batch should have e2"
|
|
);
|
|
|
|
batcher.add(a2, e3);
|
|
batcher.add(b2, e3);
|
|
batcher.add(c1, e3); // this should be ignored
|
|
let a2_b2_batch = Batch {
|
|
batch_key: a2_b2.clone(),
|
|
values: vec![e2, e3],
|
|
data: Data,
|
|
};
|
|
|
|
assert_eq!(
|
|
batcher.get_batch(&a2_b2),
|
|
Some(&a2_b2_batch),
|
|
"a2_b2 batch should have e2 and e3"
|
|
);
|
|
|
|
let mut found_a1_b1 = false;
|
|
let mut found_a2_b2 = false;
|
|
for batch in batcher.iter() {
|
|
if batch == &a1_b1_batch {
|
|
found_a1_b1 = true;
|
|
} else if batch == &a2_b2_batch {
|
|
found_a2_b2 = true;
|
|
}
|
|
}
|
|
|
|
assert!(found_a1_b1 && found_a2_b2);
|
|
assert_eq!(batcher.iter().count(), 2);
|
|
}
|
|
}
|