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 mod actions;
pub use actions::*; pub use actions::*;
/// Custom collections using Vec for cache-friendly queries
pub mod collections;
pub use collections::*;
pub mod color; pub mod color;
use bevy::ecs::query::QueryFilter; use bevy::ecs::query::QueryFilter;
pub use color::*; pub use color::*;

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

Loading…
Cancel
Save