Custom VecMap and VecSet collections for cache-friendly linear search

Does HashMap and HashSet already implement these optimizations?
Almost certainly...
main
Elijah Voigt 3 hours ago
parent 397b60b18c
commit f83b79bda5

@ -0,0 +1,124 @@
/// A vec-backed set for cache-friendly queries
#[derive(Debug)]
pub struct VecSet<T: PartialEq> {
inner: Vec<Option<T>>
}
impl<T: PartialEq> VecSet<T> {
pub fn insert(&mut self, value: T) {
// Check if value already in set, otherwise add it
if !self.contains(&value) {
self.inner.push(Some(value))
}
}
pub fn remove(&mut self, value: &T) {
self.inner.iter_mut().for_each(|val| {
if val.as_ref() == Some(value) {
*val = None;
}
})
}
pub fn compact(&mut self) {
self.inner.retain(|val| val.is_some())
}
pub fn clear(&mut self) {
self.inner.clear();
}
pub fn contains(&self, value: &T) -> bool {
self.iter().any(|v| v == value)
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.inner.iter().filter_map(|v| v.as_ref())
}
}
impl<T: PartialEq> FromIterator<T> for VecSet<T> {
fn from_iter<I: IntoIterator<Item=T>>(iter: I) -> Self {
VecSet::<T> {
inner: iter.into_iter().map(|this| Some(this)).collect()
}
}
}
impl<T: PartialEq> Default for VecSet<T> {
fn default() -> Self {
VecSet { inner: Vec::new() }
}
}
/// A vec-backed map for cache-friendly queries
#[derive(Debug)]
pub struct VecMap<K: PartialEq, V> {
inner: Vec<Option<(K, V)>>
}
impl<K: PartialEq, V> VecMap<K, V> {
pub fn insert(&mut self, key: K, val: V) {
// Check and remove/overwrite existing entry
if let Some(v) = self.get_mut(&key) {
// Update value at key
*v = val;
} else {
self.inner.push(Some((key, val)));
}
}
pub fn get(&self, key: &K) -> Option<&V> {
self.iter().find_map(|(k, v)| (k == key).then_some(v))
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
self.iter_mut().find_map(|(k, v)| (k == key).then_some(v))
}
pub fn remove(&mut self, key: &K) {
self.get_mut(key).take();
}
pub fn iter(&self) -> impl Iterator<Item = &(K,V)> {
self.inner.iter().filter_map(|kv| kv.as_ref())
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (K,V)> {
self.inner.iter_mut().filter_map(|kv| kv.as_mut())
}
pub fn compact(&mut self) {
self.inner.retain(|v| v.is_some());
}
pub fn clear(&mut self) {
self.inner.clear();
}
pub fn keys(&self) -> impl Iterator<Item = &K> {
self.inner.iter().filter_map(|this| this.as_ref().map(|(k,_)| k))
}
pub fn contains_key(&self, key: &K) -> bool {
self.keys().any(|k| k == key)
}
pub fn values(&self) -> impl Iterator<Item = &V> {
self.inner.iter().filter_map(|this| this.as_ref().map(|(_,v)| v))
}
}
impl<K: PartialEq,V> FromIterator<(K,V)> for VecMap<K,V> {
fn from_iter<I: IntoIterator<Item=(K,V)>>(iter: I) -> Self {
VecMap::<K,V> {
inner: iter.into_iter().map(|this| Some(this)).collect()
}
}
}
impl<K: PartialEq, V> Default for VecMap<K, V> {
fn default() -> Self {
VecMap { inner: Vec::new() }
}
}

@ -1,6 +1,11 @@
/// Actions handler framework
pub mod actions;
pub use actions::*;
/// Custom collections using Vec for cache-friendly queries
pub mod collections;
pub use collections::*;
pub mod color;
use bevy::ecs::query::QueryFilter;
pub use color::*;

@ -820,10 +820,10 @@ enum SimulationPlayback {
fn simulation_step(
q: Query<(Entity, &Coordinates), With<Cell>>,
mut writer: MessageWriter<Lifecycle>,
mut alive: Local<HashMap<Coordinates, Entity>>,
mut candidates: Local<HashSet<Coordinates>>,
mut alive: Local<VecMap<Coordinates, Entity>>,
mut candidates: Local<VecSet<Coordinates>>,
) {
let living_neighbors = |a: &HashMap<Coordinates, Entity>, c: &Coordinates| -> usize {
let living_neighbors = |a: &VecMap<Coordinates, Entity>, c: &Coordinates| -> usize {
c.neighbors()
.filter(|neighbor| {
a.contains_key(neighbor)
@ -938,8 +938,8 @@ fn cooldown_secs(input: f32) -> impl FnMut(Local<Timer>, Res<Time>) -> bool + Cl
#[derive(Resource, Default, Debug)]
struct CellAssets {
pitches: HashMap<CellPitch, Handle<Pitch>>,
materials: HashMap<CellPitch, Handle<ColorMaterial>>,
pitches: VecMap<CellPitch, Handle<Pitch>>,
materials: VecMap<CellPitch, Handle<ColorMaterial>>,
mesh: Handle<Mesh>,
}
@ -1077,7 +1077,7 @@ fn load_audio_tones(mut pitches: ResMut<Assets<Pitch>>, mut cell_assets: ResMut<
});
cell_assets.pitches.insert(cell_pitch, handle);
});
debug_assert_eq!(cell_assets.pitches.iter().len(), 132);
debug_assert_eq!(cell_assets.pitches.iter().count(), 132);
}
fn load_materials(
@ -1101,7 +1101,7 @@ fn load_materials(
cell_assets.materials.insert(cell_pitch, handle);
});
// 12 notes * 11 octaves = 132 materials
debug_assert_eq!(cell_assets.materials.iter().len(), 132);
debug_assert_eq!(cell_assets.materials.iter().count(), 132);
}
fn load_meshes(mut cell_assets: ResMut<CellAssets>, mut meshes: ResMut<Assets<Mesh>>) {
@ -1392,7 +1392,7 @@ fn manage_audio_players(
cell_assets: Res<CellAssets>,
pitch_map: Res<PitchMap>,
current: Query<(Entity, &CellPitch), With<AudioSink>>,
mut active: Local<HashSet<CellPitch>>,
mut active: Local<VecSet<CellPitch>>,
) {
*active = pitch_map.iter_enabled_cell_pitches().collect();
CellPitch::iter_all().for_each(|cp| {
@ -1420,7 +1420,7 @@ fn control_audio_players(
simulation_state: Res<State<SimulationPlayback>>,
mut players: Query<(&mut AudioSink, &CellPitch)>,
cells: Query<&CellPitch, With<Cell>>,
mut active_cells: Local<HashMap<CellPitch, usize>>,
mut active_cells: Local<VecMap<CellPitch, usize>>,
) {
fn turn_off(sink: &mut AudioSink) {
if !sink.is_paused() {
@ -1451,8 +1451,10 @@ fn control_audio_players(
SimulationPlayback::Run => {
// Count number of each type of cell for audio
cells.iter().for_each(|cp| {
let this = active_cells.entry(cp.clone()).or_insert(0);
*this += 1;
match active_cells.get_mut(cp) {
Some(v) => *v += 1,
None => active_cells.insert(cp.clone(), 1),
}
});
// Get the total to compute per-cell ratio

Loading…
Cancel
Save