diff --git a/Cargo.lock b/Cargo.lock index 1cc19367e7..c96bd1bca5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4996,6 +4996,7 @@ dependencies = [ "crossbeam", "dot_vox", "find_folder", + "fxhash", "hashbrown", "image", "indexmap", @@ -5118,6 +5119,7 @@ dependencies = [ "arr_macro", "bincode", "bitvec", + "fxhash", "hashbrown", "image", "itertools", diff --git a/common/Cargo.toml b/common/Cargo.toml index d55ba5e696..3a50a4e9f4 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -13,6 +13,7 @@ specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git" } specs = { version = "0.15.1", features = ["serde", "nightly", "storage-event-control"] } vek = { version = "0.10.0", features = ["serde"] } dot_vox = "4.0.0" +fxhash = "0.2.1" image = "0.22.3" mio = "0.6.19" mio-extras = "2.0.5" diff --git a/common/src/astar.rs b/common/src/astar.rs index 3ad4912733..920aeeee88 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -1,7 +1,11 @@ use crate::path::Path; -use core::cmp::Ordering::Equal; +use core::{ + cmp::Ordering::{self, Equal}, + f32, fmt, + hash::{BuildHasher, Hash}, +}; use hashbrown::{HashMap, HashSet}; -use std::{cmp::Ordering, collections::BinaryHeap, f32, hash::Hash}; +use std::collections::BinaryHeap; #[derive(Copy, Clone, Debug)] pub struct PathEntry { @@ -43,33 +47,62 @@ impl PathResult { } } -#[derive(Clone, Debug)] -pub struct Astar { +#[derive(Clone)] +pub struct Astar { iter: usize, max_iters: usize, potential_nodes: BinaryHeap>, - came_from: HashMap, - cheapest_scores: HashMap, - final_scores: HashMap, - visited: HashSet, + came_from: HashMap, + cheapest_scores: HashMap, + final_scores: HashMap, + visited: HashSet, cheapest_node: Option, cheapest_cost: Option, } -impl Astar { - pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32) -> Self { +/// NOTE: Must manually derive since Hasher doesn't implement it. +impl fmt::Debug for Astar { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Astar") + .field("iter", &self.iter) + .field("max_iters", &self.max_iters) + .field("potential_nodes", &self.potential_nodes) + .field("came_from", &self.came_from) + .field("cheapest_scores", &self.cheapest_scores) + .field("final_scores", &self.final_scores) + .field("visited", &self.visited) + .field("cheapest_node", &self.cheapest_node) + .field("cheapest_cost", &self.cheapest_cost) + .finish() + } +} + +impl Astar { + pub fn new(max_iters: usize, start: S, heuristic: impl FnOnce(&S) -> f32, hasher: H) -> Self { Self { max_iters, iter: 0, - potential_nodes: std::iter::once(PathEntry { + potential_nodes: core::iter::once(PathEntry { cost: 0.0, node: start.clone(), }) .collect(), - came_from: HashMap::default(), - cheapest_scores: std::iter::once((start.clone(), 0.0)).collect(), - final_scores: std::iter::once((start.clone(), heuristic(&start))).collect(), - visited: std::iter::once(start).collect(), + came_from: HashMap::with_hasher(hasher.clone()), + cheapest_scores: { + let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone()); + h.extend(core::iter::once((start.clone(), 0.0))); + h + }, + final_scores: { + let mut h = HashMap::with_capacity_and_hasher(1, hasher.clone()); + h.extend(core::iter::once((start.clone(), heuristic(&start)))); + h + }, + visited: { + let mut s = HashSet::with_capacity_and_hasher(1, hasher); + s.extend(core::iter::once(start)); + s + }, cheapest_node: None, cheapest_cost: None, } diff --git a/common/src/path.rs b/common/src/path.rs index a3c649fc57..b64b74e669 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -3,6 +3,7 @@ use crate::{ terrain::Block, vol::{BaseVol, ReadVol}, }; +use hashbrown::hash_map::DefaultHashBuilder; use rand::{thread_rng, Rng}; use std::iter::FromIterator; use vek::*; @@ -92,7 +93,11 @@ impl Route { pub struct Chaser { last_search_tgt: Option>, route: Route, - astar: Option>>, + /// We use this hasher (AAHasher) because: + /// (1) we care about DDOS attacks (ruling out FxHash); + /// (2) we don't care about determinism across computers (we can use + /// AAHash). + astar: Option, DefaultHashBuilder>>, } impl Chaser { @@ -147,7 +152,7 @@ impl Chaser { } fn find_path( - astar: &mut Option>>, + astar: &mut Option, DefaultHashBuilder>>, vol: &V, startf: Vec3, endf: Vec3, @@ -263,7 +268,12 @@ where let satisfied = |pos: &Vec3| pos == &end; let mut new_astar = match astar.take() { - None => Astar::new(20_000, start, heuristic.clone()), + None => Astar::new( + 20_000, + start, + heuristic.clone(), + DefaultHashBuilder::default(), + ), Some(astar) => astar, }; diff --git a/common/src/store.rs b/common/src/store.rs index 983d6a156b..8942e4a722 100644 --- a/common/src/store.rs +++ b/common/src/store.rs @@ -5,10 +5,12 @@ use std::{ ops::{Index, IndexMut}, }; -pub struct Id(usize, PhantomData); +// NOTE: We use u64 to make sure we are consistent across all machines. We +// assume that usize fits into 8 bytes. +pub struct Id(u64, PhantomData); impl Id { - pub fn id(&self) -> usize { self.0 } + pub fn id(&self) -> u64 { self.0 } } impl Copy for Id {} @@ -37,12 +39,19 @@ impl Default for Store { } impl Store { - pub fn get(&self, id: Id) -> &T { self.items.get(id.0).unwrap() } + pub fn get(&self, id: Id) -> &T { + // NOTE: Safe conversion, because it came from usize. + self.items.get(id.0 as usize).unwrap() + } - pub fn get_mut(&mut self, id: Id) -> &mut T { self.items.get_mut(id.0).unwrap() } + pub fn get_mut(&mut self, id: Id) -> &mut T { + // NOTE: Safe conversion, because it came from usize. + self.items.get_mut(id.0 as usize).unwrap() + } pub fn ids(&self) -> impl Iterator> { - (0..self.items.len()).map(|i| Id(i, PhantomData)) + // NOTE: Assumes usize fits into 8 bytes. + (0..self.items.len() as u64).map(|i| Id(i, PhantomData)) } pub fn iter(&self) -> impl Iterator { self.items.iter() } @@ -53,11 +62,13 @@ impl Store { self.items .iter() .enumerate() - .map(|(i, item)| (Id(i, PhantomData), item)) + // NOTE: Assumes usize fits into 8 bytes. + .map(|(i, item)| (Id(i as u64, PhantomData), item)) } pub fn insert(&mut self, item: T) -> Id { - let id = Id(self.items.len(), PhantomData); + // NOTE: Assumes usize fits into 8 bytes. + let id = Id(self.items.len() as u64, PhantomData); self.items.push(item); id } diff --git a/world/Cargo.toml b/world/Cargo.toml index b8bacc991d..24c424845f 100644 --- a/world/Cargo.toml +++ b/world/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" bincode = "1.2.0" common = { package = "veloren-common", path = "../common" } bitvec = "0.17.4" +fxhash = "0.2.1" image = "0.22.3" itertools = "0.8.2" vek = "0.10.0" diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 038019cec6..68a1f04d3a 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -2,6 +2,7 @@ mod econ; +use self::{Occupation::*, Stock::*}; use crate::{ config::CONFIG, sim::WorldSim, @@ -16,10 +17,15 @@ use common::{ terrain::TerrainChunkSize, vol::RectVolSize, }; +use core::{ + fmt, + hash::{BuildHasherDefault, Hash}, + ops::Range, +}; +use fxhash::FxHasher32; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; use rand_chacha::ChaChaRng; -use std::{fmt, hash::Hash, ops::Range}; use vek::*; const INITIAL_CIV_COUNT: usize = 64; @@ -30,7 +36,15 @@ pub struct Civs { places: Store, tracks: Store, - track_map: HashMap, HashMap, Id>>, + /// We use this hasher (FxHasher32) because + /// (1) we don't care about DDOS attacks (ruling out SipHash); + /// (2) we care about determinism across computers (ruling out AAHash); + /// (3) we have 8-byte keys (for which FxHash is fastest). + track_map: HashMap< + Id, + HashMap, Id, BuildHasherDefault>, + BuildHasherDefault, + >, sites: Store, } @@ -241,7 +255,16 @@ impl Civs { let transition = |a: &Id, b: &Id| self.tracks.get(self.track_between(*a, *b).unwrap()).cost; let satisfied = |p: &Id| *p == b; - let mut astar = Astar::new(100, a, heuristic); + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 8-byte keys (for which FxHash is fastest). + let mut astar = Astar::new( + 100, + a, + heuristic, + BuildHasherDefault::::default(), + ); astar .poll(100, heuristic, neighbors, transition, satisfied) .into_path() @@ -287,8 +310,12 @@ impl Civs { loc: Vec2, area: Range, ) -> Option> { - let mut dead = HashSet::new(); - let mut alive = HashSet::new(); + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 8-byte keys (for which FxHash is fastest). + let mut dead = HashSet::with_hasher(BuildHasherDefault::::default()); + let mut alive = HashSet::with_hasher(BuildHasherDefault::::default()); alive.insert(loc); // Fill the surrounding area @@ -501,7 +528,16 @@ fn find_path( let transition = |a: &Vec2, b: &Vec2| 1.0 + walk_in_dir(sim, *a, *b - *a).unwrap_or(10000.0); let satisfied = |l: &Vec2| *l == b; - let mut astar = Astar::new(20000, a, heuristic); + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 8-byte keys (for which FxHash is fastest). + let mut astar = Astar::new( + 20000, + a, + heuristic, + BuildHasherDefault::::default(), + ); astar .poll(20000, heuristic, neighbors, transition, satisfied) .into_path() @@ -694,13 +730,13 @@ impl fmt::Display for Site { writeln!(f, "- coin: {}", self.coin.floor() as u32)?; writeln!(f, "Stocks")?; for (stock, q) in self.stocks.iter() { - writeln!(f, "- {}: {}", stock, q.floor())?; + writeln!(f, "- {:?}: {}", stock, q.floor())?; } writeln!(f, "Values")?; for stock in TRADE_STOCKS.iter() { writeln!( f, - "- {}: {}", + "- {:?}: {}", stock, self.values[*stock] .map(|x| x.to_string()) @@ -709,11 +745,16 @@ impl fmt::Display for Site { } writeln!(f, "Laborers")?; for (labor, n) in self.labors.iter() { - writeln!(f, "- {}: {}", labor, (*n * self.population).floor() as u32)?; + writeln!( + f, + "- {:?}: {}", + labor, + (*n * self.population).floor() as u32 + )?; } writeln!(f, "Export targets")?; for (stock, n) in self.export_targets.iter() { - writeln!(f, "- {}: {}", stock, n)?; + writeln!(f, "- {:?}: {}", stock, n)?; } Ok(()) @@ -729,43 +770,50 @@ pub enum SiteKind { impl Site { pub fn simulate(&mut self, years: f32, nat_res: &NaturalResources) { // Insert natural resources into the economy - if self.stocks[FISH] < nat_res.river { - self.stocks[FISH] = nat_res.river; + if self.stocks[Fish] < nat_res.river { + self.stocks[Fish] = nat_res.river; } - if self.stocks[WHEAT] < nat_res.farmland { - self.stocks[WHEAT] = nat_res.farmland; + if self.stocks[Wheat] < nat_res.farmland { + self.stocks[Wheat] = nat_res.farmland; } - if self.stocks[LOGS] < nat_res.wood { - self.stocks[LOGS] = nat_res.wood; + if self.stocks[Logs] < nat_res.wood { + self.stocks[Logs] = nat_res.wood; } - if self.stocks[GAME] < nat_res.wood { - self.stocks[GAME] = nat_res.wood; + if self.stocks[Game] < nat_res.wood { + self.stocks[Game] = nat_res.wood; } - if self.stocks[ROCK] < nat_res.rock { - self.stocks[ROCK] = nat_res.rock; + if self.stocks[Rock] < nat_res.rock { + self.stocks[Rock] = nat_res.rock; } + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 1-byte keys (for which FxHash is supposedly fastest). let orders = vec![ - (None, vec![(FOOD, 0.5)]), - (Some(COOK), vec![(FLOUR, 16.0), (MEAT, 4.0), (WOOD, 3.0)]), - (Some(LUMBERJACK), vec![(LOGS, 4.5)]), - (Some(MINER), vec![(ROCK, 7.5)]), - (Some(FISHER), vec![(FISH, 4.0)]), - (Some(HUNTER), vec![(GAME, 4.0)]), - (Some(FARMER), vec![(WHEAT, 4.0)]), + (None, vec![(Food, 0.5)]), + (Some(Cook), vec![(Flour, 16.0), (Meat, 4.0), (Wood, 3.0)]), + (Some(Lumberjack), vec![(Logs, 4.5)]), + (Some(Miner), vec![(Rock, 7.5)]), + (Some(Fisher), vec![(Fish, 4.0)]), + (Some(Hunter), vec![(Game, 4.0)]), + (Some(Farmer), vec![(Wheat, 4.0)]), ] .into_iter() - .collect::>>(); + .collect::, BuildHasherDefault>>(); // Per labourer, per year - let production = Stocks::from_list(&[ - (FARMER, (FLOUR, 2.0)), - (LUMBERJACK, (WOOD, 1.5)), - (MINER, (STONE, 0.6)), - (FISHER, (MEAT, 3.0)), - (HUNTER, (MEAT, 0.25)), - (COOK, (FOOD, 20.0)), - ]); + let production = MapVec::from_list( + &[ + (Farmer, (Flour, 2.0)), + (Lumberjack, (Wood, 1.5)), + (Miner, (Stone, 0.6)), + (Fisher, (Meat, 3.0)), + (Hunter, (Meat, 0.25)), + (Cook, (Food, 20.0)), + ], + (Rock, 0.0), + ); let mut demand = Stocks::from_default(0.0); for (labor, orders) in &orders { @@ -887,7 +935,7 @@ impl Site { // Births/deaths const NATURAL_BIRTH_RATE: f32 = 0.15; const DEATH_RATE: f32 = 0.05; - let birth_rate = if self.surplus[FOOD] > 0.0 { + let birth_rate = if self.surplus[Food] > 0.0 { NATURAL_BIRTH_RATE } else { 0.0 @@ -896,26 +944,33 @@ impl Site { } } -type Occupation = &'static str; -const FARMER: Occupation = "farmer"; -const LUMBERJACK: Occupation = "lumberjack"; -const MINER: Occupation = "miner"; -const FISHER: Occupation = "fisher"; -const HUNTER: Occupation = "hunter"; -const COOK: Occupation = "cook"; +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +enum Occupation { + Farmer = 0, + Lumberjack = 1, + Miner = 2, + Fisher = 3, + Hunter = 4, + Cook = 5, +} -type Stock = &'static str; -const WHEAT: Stock = "wheat"; -const FLOUR: Stock = "flour"; -const MEAT: Stock = "meat"; -const FISH: Stock = "fish"; -const GAME: Stock = "game"; -const FOOD: Stock = "food"; -const LOGS: Stock = "logs"; -const WOOD: Stock = "wood"; -const ROCK: Stock = "rock"; -const STONE: Stock = "stone"; -const TRADE_STOCKS: [Stock; 5] = [FLOUR, MEAT, FOOD, WOOD, STONE]; +#[repr(u8)] +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Stock { + Wheat = 0, + Flour = 1, + Meat = 2, + Fish = 3, + Game = 4, + Food = 5, + Logs = 6, + Wood = 7, + Rock = 8, + Stone = 9, +} + +const TRADE_STOCKS: [Stock; 5] = [Flour, Meat, Food, Wood, Stone]; #[derive(Debug, Clone)] struct TradeState { @@ -940,21 +995,35 @@ impl Default for TradeState { pub type Stocks = MapVec; -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct MapVec { - entries: HashMap, + /// We use this hasher (FxHasher32) because + /// (1) we don't care about DDOS attacks (ruling out SipHash); + /// (2) we care about determinism across computers (ruling out AAHash); + /// (3) we have 1-byte keys (for which FxHash is supposedly fastest). + entries: HashMap>, default: T, } -impl MapVec { - pub fn from_list<'a>(i: impl IntoIterator) -> Self +/// Need manual implementation of Default since K doesn't need that bound. +impl Default for MapVec { + fn default() -> Self { + Self { + entries: Default::default(), + default: Default::default(), + } + } +} + +impl MapVec { + pub fn from_list<'a>(i: impl IntoIterator, default: T) -> Self where K: 'a, T: 'a, { Self { entries: i.into_iter().cloned().collect(), - default: T::default(), + default, } } @@ -992,12 +1061,12 @@ impl MapVec { } } -impl std::ops::Index for MapVec { +impl std::ops::Index for MapVec { type Output = T; fn index(&self, entry: K) -> &Self::Output { self.get(entry) } } -impl std::ops::IndexMut for MapVec { +impl std::ops::IndexMut for MapVec { fn index_mut(&mut self, entry: K) -> &mut Self::Output { self.get_mut(entry) } } diff --git a/world/src/sim/location.rs b/world/src/sim/location.rs index 2eeb1c7a3d..c3d7d391f8 100644 --- a/world/src/sim/location.rs +++ b/world/src/sim/location.rs @@ -1,3 +1,5 @@ +use core::hash::BuildHasherDefault; +use fxhash::FxHasher32; use hashbrown::HashSet; use rand::{seq::SliceRandom, Rng}; use vek::*; @@ -7,7 +9,11 @@ pub struct Location { pub(crate) name: String, pub(crate) center: Vec2, pub(crate) kingdom: Option, - pub(crate) neighbours: HashSet, + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 8-byte keys (for which FxHash is fastest). + pub(crate) neighbours: HashSet>, } impl Location { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 675008713c..6947c3925a 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -1448,14 +1448,15 @@ impl WorldSim { .map(|l| l.center) .enumerate() .collect::>(); + // NOTE: We assume that usize is 8 or fewer bytes. (0..locations.len()).for_each(|i| { let pos = locations[i].center.map(|e| e as i64); loc_clone.sort_by_key(|(_, l)| l.map(|e| e as i64).distance_squared(pos)); loc_clone.iter().skip(1).take(2).for_each(|(j, _)| { - locations[i].neighbours.insert(*j); - locations[*j].neighbours.insert(i); + locations[i].neighbours.insert(*j as u64); + locations[*j].neighbours.insert(i as u64); }); }); diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 16dd5ed58b..cc44a179bf 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -16,9 +16,11 @@ use common::{ terrain::{Block, BlockKind, Structure, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; +use core::{f32, hash::BuildHasherDefault}; +use fxhash::FxHasher32; use lazy_static::lazy_static; use rand::prelude::*; -use std::{f32, sync::Arc}; +use std::sync::Arc; use vek::*; impl WorldSim { @@ -381,7 +383,16 @@ impl Floor { _ => 100000.0, }; let satisfied = |l: &Vec2| *l == b; - let mut astar = Astar::new(20000, a, heuristic); + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we don't care about determinism across computers (we could use AAHash); + // (3) we have 4-byte keys (for which FxHash is fastest). + let mut astar = Astar::new( + 20000, + a, + heuristic, + BuildHasherDefault::::default(), + ); let path = astar .poll( FLOOR_SIZE.product() as usize + 1, diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 3fafa933d7..c653bbdfff 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -18,9 +18,10 @@ use common::{ terrain::{Block, BlockKind, TerrainChunkSize}, vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol}, }; +use fxhash::FxHasher32; use hashbrown::{HashMap, HashSet}; use rand::prelude::*; -use std::{collections::VecDeque, f32}; +use std::{collections::VecDeque, f32, hash::BuildHasherDefault}; use vek::*; #[allow(dead_code)] @@ -967,7 +968,11 @@ pub struct Sample<'a> { } pub struct Land { - tiles: HashMap, Tile>, + /// We use this hasher (FxHasher32) because + /// (1) we need determinism across computers (ruling out AAHash); + /// (2) we don't care about DDOS attacks (ruling out SipHash); + /// (3) we have 4-byte keys (for which FxHash is fastest). + tiles: HashMap, Tile, BuildHasherDefault>, plots: Store, sampler_warp: StructureGen2d, hazard: Id, @@ -978,7 +983,7 @@ impl Land { let mut plots = Store::default(); let hazard = plots.insert(Plot::Hazard); Self { - tiles: HashMap::new(), + tiles: HashMap::default(), plots, sampler_warp: StructureGen2d::new(rng.gen(), AREA_SIZE, AREA_SIZE * 2 / 5), hazard, @@ -1089,21 +1094,38 @@ impl Land { |from: &Vec2, to: &Vec2| path_cost_fn(self.tile_at(*from), self.tile_at(*to)); let satisfied = |pos: &Vec2| *pos == dest; - Astar::new(250, origin, heuristic) - .poll(250, heuristic, neighbors, transition, satisfied) - .into_path() + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we don't care about determinism across computers (we could use AAHash); + // (3) we have 4-byte keys (for which FxHash is fastest). + Astar::new( + 250, + origin, + heuristic, + BuildHasherDefault::::default(), + ) + .poll(250, heuristic, neighbors, transition, satisfied) + .into_path() } + /// We use this hasher (FxHasher32) because + /// (1) we don't care about DDOS attacks (ruling out SipHash); + /// (2) we care about determinism across computers (ruling out AAHash); + /// (3) we have 8-byte keys (for which FxHash is fastest). fn grow_from( &self, start: Vec2, max_size: usize, _rng: &mut impl Rng, mut match_fn: impl FnMut(Option<&Plot>) -> bool, - ) -> HashSet> { + ) -> HashSet, BuildHasherDefault> { let mut open = VecDeque::new(); open.push_back(start); - let mut closed = HashSet::new(); + // We use this hasher (FxHasher32) because + // (1) we don't care about DDOS attacks (ruling out SipHash); + // (2) we care about determinism across computers (ruling out AAHash); + // (3) we have 8-byte keys (for which FxHash is fastest). + let mut closed = HashSet::with_hasher(BuildHasherDefault::::default()); while open.len() + closed.len() < max_size { let next_pos = if let Some(next_pos) = open.pop_front() { diff --git a/world/src/util/hash_cache.rs b/world/src/util/hash_cache.rs deleted file mode 100644 index ae669f74c3..0000000000 --- a/world/src/util/hash_cache.rs +++ /dev/null @@ -1,45 +0,0 @@ -use hashbrown::HashMap; -use std::hash::Hash; - -pub struct HashCache { - capacity: usize, - map: HashMap, - counter: usize, -} - -impl Default for HashCache { - fn default() -> Self { Self::with_capacity(1024) } -} - -impl HashCache { - pub fn with_capacity(capacity: usize) -> Self { - Self { - capacity, - map: HashMap::with_capacity(1024), - counter: 0, - } - } - - pub fn maintain(&mut self) { - const CACHE_BLOAT_RATE: usize = 2; - - if self.map.len() > self.capacity * CACHE_BLOAT_RATE { - let (capacity, counter) = (self.capacity, self.counter); - self.map.retain(|_, (c, _)| *c + capacity > counter); - } - } - - pub fn get V>(&mut self, key: K, f: F) -> &V { - self.maintain(); - - let counter = &mut self.counter; - &self - .map - .entry(key.clone()) - .or_insert_with(|| { - *counter += 1; - (*counter, f(key)) - }) - .1 - } -} diff --git a/world/src/util/mod.rs b/world/src/util/mod.rs index ce4dbd3971..9ab6f91dce 100644 --- a/world/src/util/mod.rs +++ b/world/src/util/mod.rs @@ -1,6 +1,5 @@ pub mod fast_noise; pub mod grid; -pub mod hash_cache; pub mod random; pub mod sampler; pub mod seed_expan; @@ -12,7 +11,6 @@ pub mod unit_chooser; pub use self::{ fast_noise::FastNoise, grid::Grid, - hash_cache::HashCache, random::{RandomField, RandomPerm}, sampler::{Sampler, SamplerMut}, small_cache::SmallCache,